1 module gui;
2 
3 import core.thread;
4 import gtk.MainWindow;
5 import gtk.ListBox;
6 import gtk.Label;
7 import gtk.Notebook;
8 import gdk.Threads : te = threadsEnter, tl = threadsLeave;
9 import gtk.MenuBar;
10 import gtk.Box;
11 import gtk.Menu;
12 import gtk.MenuItem;
13 import std.stdio;
14 import gtk.Statusbar;
15 import gtk.Toolbar;
16 import gtk.ToolButton;
17 import gtk.ScrolledWindow;
18 import gtk.SeparatorToolItem;
19 import gtk.ToolItem;
20 import gtk.SearchEntry;
21 import gtk.Image;
22 
23 import Connection;
24 import areas.Channel;
25 import std.socket;
26 
27 import std.conv;
28 
29 public class GUI : Thread
30 {
31     /* Main window (GUI homepage) */
32     public MainWindow mainWindow;
33     private MenuBar menuBar;
34     private Toolbar toolbar;
35 
36     private Box box;
37     private Box welcomeBox;
38 
39     public Notebook notebook;
40 
41 
42 
43     private Statusbar statusBar;
44 
45 
46 
47 
48     private Connection[] connections;
49 
50     private ListBox list;
51     
52 
53     this()
54     {
55         super(&worker);
56     }
57 
58     private void worker()
59     {
60         initializeGUI();
61 
62 
63         te();
64         
65         tl();
66         writeln("brg");
67         while(true)
68         {
69 
70         }
71     }
72 
73     private void initializeGUI()
74     {
75         initializeMainWindow();
76     }
77 
78 
79     /**
80     * The welcome box is shown before
81     * you have added any connections
82     * (it takes place of the Notebook)
83     * and shows information about the
84     * application
85     *
86     * Once you make your first conneciton
87     * it is removed and its space is taken
88     * up by the Notebook
89     */
90     private Box getWelcomeBox()
91     {
92         /* Create a vertically stacking Box */
93         Box welcomeBox = new Box(GtkOrientation.VERTICAL, 1);
94 
95         /* Add the logo */
96         Image logo = new Image("user-available", GtkIconSize.DIALOG);
97         logo.setPixelSize(250);
98         welcomeBox.add(logo);
99 
100         /* Create the welcome text */
101         Label title = new Label("<span size=\"100\">Gustav</span>");
102         title.setMarkup("<span font_desc=\"Open Sans Extrabold\" size=\"50000\">Gustav</span>");
103         welcomeBox.add(title);
104 
105         /* Create the welcome tagline */
106         Label tagline = new Label("<span size=\"100\">Gustav</span>");
107         tagline.setMarkup("<span size=\"30000\">GTK+ graphical DNET client</span>");
108         welcomeBox.add(tagline);
109 
110         Label findServersLabel = new Label("<a href=\"\">fok</a>");
111         findServersLabel.setMarkup("<a href=\"\">Find some servers</a>");
112         welcomeBox.add(findServersLabel);
113 
114         Label configureConnectionsLabel = new Label("<a href=\"\">Configure connections</a>");
115         configureConnectionsLabel.setMarkup("<a href=\"\">Configure connections</a>");
116         configureConnectionsLabel.addOnActivateLink(&conifgureConnectionsAssistant);
117         welcomeBox.add(configureConnectionsLabel);
118 
119         Label connectGenesisLabel = new Label("<a href=\"\">Connect to the genesis server</a>");
120         connectGenesisLabel.setMarkup("<span size=\"12000\"> <a href=\"\">Connect to the genesis server</a></span>");
121         connectGenesisLabel.addOnActivateLink(&welcomeGenesisLabelClick);
122         welcomeBox.add(connectGenesisLabel);
123 
124         
125 
126         return welcomeBox;
127     }
128 
129     private bool welcomeGenesisLabelClick(string, Label)
130     {
131         connectServer("0.0.0.0", 7777, ["testGustav"~to!(string)(connections.length), "bruh"]);
132 
133         return 1;
134     }
135 
136     /**
137     * Initializes the main home screen window
138     */
139     private void initializeMainWindow()
140     {
141         /* Get GTK lock */
142         te();
143 
144         /* Create a window */
145         mainWindow = new MainWindow("unamed");
146 
147         /**
148         * Create a Box in vertical layout mode
149         * and adds it to the window
150         *
151         * This lays out components like so:
152         *
153         * |component 1|
154         * |component 2|
155         */
156         box = new Box(GtkOrientation.VERTICAL, 1);
157 
158         /**
159         * Add needed components
160         *
161         * Menubar, tabbed pane switcher, statusbar
162         */
163         menuBar = initializeMenuBar();
164         box.add(menuBar);
165 
166         toolbar = getToolbar();
167         box.add(toolbar);
168 
169         /* Create the welcome box and set it */
170         welcomeBox = getWelcomeBox();
171         box.add(welcomeBox);
172         
173         
174         
175         statusBar = new Statusbar();
176         statusBar.add(new Label("Gustav: Bruh"));
177         // import gtk.IconView;
178         // IconView j = new IconView();
179         // j.set
180         // statusBar.add(d);
181         
182 
183 
184         
185         box.packEnd(statusBar, 0, 0, 0);
186         //notebook.add(createServerTab());
187         
188 
189 
190 
191         /* Add the Box to main window */
192         mainWindow.add(box);
193 
194         mainWindow.showAll();
195 
196         /* Unlock GTK lock */
197         tl();
198 
199         writeln("unlock gui setup");
200     }
201 
202     private Toolbar getToolbar()
203     {
204         /* Create a new Toolbar */
205         Toolbar toolbar = new Toolbar();
206 
207         /* Status selector dropdown */
208         /* TODO */
209 
210 
211         /* Set available button */
212         ToolButton setAvail = new ToolButton("");
213         setAvail.setLabel("available");
214         setAvail.setIconName("user-available");
215         toolbar.add(setAvail);
216 
217         /* Set away button */
218         ToolButton setAway = new ToolButton("");
219         setAway.setLabel("away");
220         setAway.setIconName("user-away");
221         toolbar.add(setAway);
222 
223         /* Set busy button */
224         ToolButton setBusy = new ToolButton("");
225         setBusy.setLabel("busy");
226         setBusy.setIconName("user-busy");
227         toolbar.add(setBusy);
228 
229 
230         /* Assign actions */
231         setAvail.addOnClicked(&setStatus);
232         setAway.addOnClicked(&setStatus);
233         setBusy.addOnClicked(&setStatus);
234 
235 
236         /* The status box */
237         Entry statusBox = new Entry();
238         statusBox.addOnActivate(&setStatusMessage);
239         statusBox.setPlaceholderText("I'm currently...");
240         ToolItem statusBoxItem = new ToolItem();
241         statusBoxItem.add(statusBox);
242         toolbar.add(statusBoxItem);
243 
244 
245         /* Add a seperator */
246         toolbar.add(new SeparatorToolItem());
247 
248 
249         /* List channels button */
250         ToolButton channelListButton = new ToolButton("");
251         channelListButton.setIconName("emblem-documents");
252         channelListButton.setTooltipText("List channels");
253         channelListButton.addOnClicked(&listChannels);
254         toolbar.add(channelListButton);
255 
256         /* TODO: Join channel button */
257         ToolButton joinChannelButton  =  new ToolButton("");
258         joinChannelButton.setIconName("document-new");
259         joinChannelButton.setTooltipText("Join channel");
260         toolbar.add(joinChannelButton);
261 
262 
263         /* TODO: Leave channel button */
264 
265 
266 
267         
268 
269 
270         SearchEntry dd = new SearchEntry();
271         ToolItem j = new ToolItem();
272         j.add(dd);
273         toolbar.add(j);
274         
275 
276 
277 
278 
279         
280         
281        
282 
283 
284         return toolbar;
285     }
286 
287     import gtk.Entry;
288     import std.string;
289     private void setStatusMessage(Entry f)
290     {
291         /* If there are no connections */
292         if(!connections.length)
293         {
294             import gtk.MessageDialog;
295             MessageDialog errorDialog = new MessageDialog(mainWindow, GtkDialogFlags.MODAL, GtkMessageType.ERROR, GtkButtonsType.CLOSE, false, "Cannot set status\n\nYou are not connected to a server");
296             errorDialog.setIconName("user-available");
297             // errorDialog.set
298             errorDialog.run();
299             return;
300         }
301 
302 
303         /* Get the current connection */
304         Connection currentConnection = connections[notebook.getCurrentPage()];
305 
306         /* Get the input text (removing leading and trailing whitespace) */
307         string statusTextInput = f.getBuffer().getText();
308         statusTextInput = strip(statusTextInput);
309 
310         /* Set the text box to the stripped version */
311         //f.getBuffer().setText(statusTextInput, cast(int)statusTextInput.length);
312 
313         /* If the status text is empty */
314         if(cmp(statusTextInput, "") == 0)
315         {
316             /* Delete the status property */
317             currentConnection.getClient().deleteProperty("status");
318         }
319         /* If the status text is non empty */
320         else
321         {
322             /* Set the status */
323             currentConnection.getClient().setProperty("status", statusTextInput);
324         }
325 
326         //f.setInputHints(GtkInputHints.)
327 
328         /* Defocus the currently focused widget which would always be me if you are hitting enter */
329         mainWindow.setFocus(null);
330     }
331 
332     private void about(MenuItem)
333     {
334         import gtk.AboutDialog;
335         AboutDialog about = new AboutDialog();
336 
337         about.setVersion("21893");
338 
339         /* TODO: License */
340         /* TODO: Icon */
341         /* TODO: Buttons or close */
342         /* TODO: Set version based on compiler flag */
343 
344         about.setLogoIconName("user-available");
345         about.setArtists(["i wonder if I could commision an artwork from her"]);
346 
347         /* Set all the information */
348         about.setLicense("LICENSE GOES HERE");
349         about.setComments("A clean GTK+ graphical DNET client");
350         about.setWebsite("http://deavmi.assigned.network/docs/dnet/site");
351         about.setDocumenters(["ss","fdsfsd"]);
352         about.setAuthors(["Tristan B. Kildaire (Deavmi) - deavmi@disroot.org"]);
353 
354         /* Show the about dialog */
355         about.showAll();
356     }
357 
358     import gtk.Button;
359 
360     /**
361     * Returns a Box which contains channel list item
362     */
363 
364     private class JoinButton : Button
365     {
366         private string channelName;
367         this(string channelName)
368         {
369             this.channelName = channelName;
370         }
371         public string getChannelName()
372         {
373             return channelName;
374         }
375     }
376     private Box channelItemList(Connection currentConnection, string channelName)
377     {
378         /* Create the main container */
379         Box containerMain = new Box(GtkOrientation.HORIZONTAL, 1);
380 
381 
382 
383         /* Add the channel label */
384         Label channelLabel = new Label("");
385         channelLabel.setHalign(GtkAlign.START);
386         channelLabel.setMarkup("<b>"~channelName~"</b>");
387 
388         /* Add the member count */
389         ulong memberCount = currentConnection.getClient().getMemberCount(channelName);
390         Label memberCountLabel = new Label("");
391         memberCountLabel.setHalign(GtkAlign.START);
392         memberCountLabel.setText(to!(string)(memberCount)~" members");
393 
394         /* Create the channel box */
395         Box channelBox = new Box(GtkOrientation.VERTICAL, 1);
396         channelBox.add(channelLabel);
397         channelBox.add(memberCountLabel);
398 
399         /* Join button */
400         JoinButton joinButton = new JoinButton(channelName);
401         joinButton.setLabel("Join");
402         
403 
404 
405         /* Add this then a button */
406         containerMain.add(channelBox);
407         containerMain.packEnd(joinButton,0,0,0);
408 
409         
410         
411         joinButton.addOnClicked(&selectChannel);
412 
413         
414 
415         /* TODO: COnsider adding member list */
416         /* TODO: Seperate queue for dynamic updates to this list */
417         containerMain.setTooltipMarkup("<b>"~channelName~"</b>\n"~to!(string)(memberCount)~" members\n\n"~to!(string)(currentConnection.getClient().getMembers(channelName)));
418 
419         return containerMain;
420     }
421 
422 
423     private class JoinButtonCustom : Button
424     {
425         private Entry channelInputBox;
426 
427         this(Entry channelInputBox)
428         {
429             /* Set the button's text to "Join" */
430             super("Join");
431 
432             /* Set the handler for the button */
433             addOnClicked(&handler);
434 
435             this.channelInputBox = channelInputBox;
436         }
437 
438         private void handler(Button)
439         {
440             /* Get the current connection */
441             Connection currentConnection = connections[notebook.getCurrentPage()];
442 
443             /* Get the name of the channel selected */
444             string channelSelected = channelInputBox.getText();
445 
446             /* Join the channel on this connection */
447             currentConnection.joinChannel(channelSelected);
448         }
449     }
450 
451     /**
452     * List channels
453     *
454     * Brings up a window listing channels of the current server
455     */
456     private void listChannels(ToolButton)
457     {
458         import gtk.Window;
459 
460         /* Create the window */
461         Window win = new Window(GtkWindowType.TOPLEVEL);
462 
463         /* Create the list of channels */
464         ListBox channelsList = new ListBox();
465         win.add(new ScrolledWindow(channelsList));
466 
467 
468 
469 
470         /* TODO: Temporary, REMOVE AFTWR TESTING (ADDED ON 27th of JAN 2021) */
471         Box box = new Box(GtkOrientation.HORIZONTAL, 1);
472         Entry customChannelEntry = new Entry();
473         box.packStart(customChannelEntry, 1, 1, 1);
474         box.add(new JoinButtonCustom(customChannelEntry));
475         channelsList.add(box);
476 
477         /* If there are no available connections */
478         if(!connections.length)
479         {
480             import gtk.MessageDialog;
481             MessageDialog errorDialog = new MessageDialog(mainWindow, GtkDialogFlags.MODAL, GtkMessageType.ERROR, GtkButtonsType.CLOSE, false, "Cannot list channels\n\nYou are not connected to a server");
482             errorDialog.setIconName("user-available");
483             // errorDialog.set
484             errorDialog.run();
485 
486             return;
487         }
488 
489 
490         /* Get the current connection */
491         Connection currentConnection = connections[notebook.getCurrentPage()];
492 
493         /* Fetch the channels */
494         string[] channels = currentConnection.getClient().list();
495 
496         /* Add each channel */
497         foreach(string channel; channels)
498         {
499             // channelsList.add(new Label(channel));
500             channelsList.add(channelItemList(currentConnection, channel));
501             writeln("bruh: "~channel);
502             channelsList.showAll();
503         }
504 
505         /* TODO: Add handler for clicking label that lets you join the channel */
506         // channelsList.addOnSelectedRowsChanged(&selectChannel);
507         //channelsList.add
508 
509 
510         win.showAll();
511     }
512 
513     /**
514     * Opens a new window for connecting to a server
515     */
516     private void connect(MenuItem)
517     {
518         import gtk.Window;
519 
520         /* Create the window */
521         Window win = new Window(GtkWindowType.TOPLEVEL);
522 
523         
524         //import gtk.Text
525 
526         
527 
528         win.showAll();
529     }
530 
531     /**
532     * Run when you select the 'Join' button next tio an already existing
533     * channel in the channels list
534     */
535     private void selectChannel(Button s)
536     {
537         /* Get the current connection */
538         Connection currentConnection = connections[notebook.getCurrentPage()];
539 
540         /* Get the name of the channel selected */
541         string channelSelected = (cast(JoinButton)s).getChannelName(); //(cast(Label)(s.getSelectedRow().getChild())).getText();
542 
543         /* Join the channel on this connection */
544         currentConnection.joinChannel(channelSelected);
545     }
546 
547     private bool conifgureConnectionsAssistant(string, Label)
548     {
549         import ConnectionAssistant;
550         ConnectionAssistant ass = new ConnectionAssistant(this);
551         return 0;
552     }
553 
554     private void setStatus(ToolButton x)
555     {
556         /* If there are any available connections */
557         if(connections.length)
558         {
559             /* Get the current connection */
560             Connection currentConnection = connections[notebook.getCurrentPage()];
561 
562             /* Set the status */
563             currentConnection.getClient().setProperty("pres", x.getLabel()); 
564         }
565         /* If there are no connections */
566         else
567         {
568             import gtk.MessageDialog;
569             MessageDialog errorDialog = new MessageDialog(mainWindow, GtkDialogFlags.MODAL, GtkMessageType.ERROR, GtkButtonsType.CLOSE, false, "Cannot set prescence\n\nYou are not connected to a server");
570             errorDialog.setIconName("user-available");
571             // errorDialog.set
572             errorDialog.run();
573         }
574     }
575 
576     private MenuBar initializeMenuBar()
577     {
578         MenuBar menuBar = new MenuBar();
579 
580         /* Gustav menu */
581         MenuItem gustavMenuItem = new MenuItem();
582         gustavMenuItem.setLabel("Gustav");
583         Menu gustavMenu = new Menu();
584         gustavMenuItem.setSubmenu(gustavMenu);
585         
586         /* Connect option */
587         MenuItem connectItem = new MenuItem();
588         connectItem.setLabel("Connect");
589         connectItem.addOnActivate(&connectButton);
590         gustavMenu.add(connectItem);
591 
592         /* Connect v2 option */
593         MenuItem connectItem2 = new MenuItem();
594         connectItem2.setLabel("Connect");
595         connectItem2.addOnActivate(&connect);
596         gustavMenu.add(connectItem2);
597 
598         
599 
600         /* Exit option */
601         MenuItem exitItem = new MenuItem();
602         exitItem.setLabel("Exit");
603         exitItem.addOnActivate(&exitButton);
604         gustavMenu.add(exitItem);
605 
606 
607         /* Help menu */
608         MenuItem helpMenuItem = new MenuItem();
609         helpMenuItem.setLabel("Help");
610         Menu helpMenu = new Menu();
611         helpMenuItem.setSubmenu(helpMenu);
612 
613         /* About option */
614         MenuItem aboutItem = new MenuItem();
615         aboutItem.setLabel("About");
616         aboutItem.addOnActivate(&about);
617         helpMenu.add(aboutItem);
618 
619         
620 
621         
622 
623         /* Add all menues */
624         menuBar.add(gustavMenuItem);
625         menuBar.add(helpMenuItem);
626 
627         return menuBar;
628     }
629 
630     private void exitButton(MenuItem)
631     {
632         writeln("bruh");
633 
634         /* TODO: Implement exit */
635 
636         
637 // tl();
638         //te();
639 
640         
641         shutdownConnections();
642 
643 
644        // mainWindow.showAll();
645 
646        // tl();
647     }
648 
649     private void connectButton(MenuItem)
650     {
651         connectServer("0.0.0.0", 7777, ["testGustav"~to!(string)(connections.length), "bruh"]);
652     }
653 
654     /**
655     * Connects to the provided server,
656     * add the tab as well
657     *
658     * NOTE: To be called only by a GTK signal
659     * handler
660     */
661     public void connectServer(string address, ushort port, string[] authDetails)
662     {
663         /**
664         * If this is our first connection then
665         * create a new Notebook which will
666         * hold the connection/session tabs
667         * and remove the welcome page
668         */
669         if(!notebook)
670         {
671             notebook = new Notebook();
672             notebook.setScrollable(true);
673             box.add(notebook);
674             box.setChildPacking(notebook, true, true, 0, GtkPackType.START);
675             box.remove(welcomeBox);
676             box.showAll();
677         }
678        
679         /* Create the new Connection */
680         Connection newConnection = new Connection(this, parseAddress(address, port), authDetails);
681         connections ~= newConnection;
682 
683         // import UserDirectory;
684         // UserDirectory d = new UserDirectory(newConnection);
685 
686     }
687 
688     private void shutdownConnections()
689     {
690         foreach(Connection connection; connections)
691         {
692             /**
693             * TODO: This is called by signal handler, we need no mutexes for signal handler
694             * hence it means that connection
695             */
696             connection.shutdown();
697             Thread.sleep(dur!("seconds")(2));
698         }
699     }
700 
701     private void newServer()
702     {
703 
704     }
705 
706     private Box createServerTab()
707     {
708         Box serverTab = new Box(GtkOrientation.HORIZONTAL, 1);
709 
710         serverTab.add(new Label("hello"));
711 
712         // serverTab.add();
713 
714         return serverTab;
715     }
716 }