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 }