~swisschili/chat

b8216b8bb81a9436e955206592a5d85bd085e75d — swissChili 4 months ago 02fc427
Add auto-scroll, join button, add channel dialog
A client/src/main/java/sh/swisschili/chat/client/AddChannelDialog.form => client/src/main/java/sh/swisschili/chat/client/AddChannelDialog.form +88 -0
@@ 0,0 1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="sh.swisschili.chat.client.AddChannelDialog">
  <grid id="27dc6" binding="panel" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
    <margin top="0" left="0" bottom="0" right="0"/>
    <constraints>
      <xy x="20" y="20" width="474" height="201"/>
    </constraints>
    <properties/>
    <border type="none"/>
    <children>
      <component id="ed1b0" class="javax.swing.JLabel">
        <constraints>
          <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
        </constraints>
        <properties>
          <text value="Create channel"/>
        </properties>
      </component>
      <grid id="7d1a3" layout-manager="FormLayout">
        <rowspec value="center:d:noGrow"/>
        <colspec value="fill:d:noGrow"/>
        <colspec value="left:4dlu:noGrow"/>
        <colspec value="fill:d:grow"/>
        <constraints>
          <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
        </constraints>
        <properties/>
        <border type="none"/>
        <children>
          <component id="2a894" class="javax.swing.JLabel">
            <constraints>
              <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
              <forms/>
            </constraints>
            <properties>
              <text value="Name"/>
            </properties>
          </component>
          <component id="7d7e5" class="javax.swing.JTextField" binding="name">
            <constraints>
              <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
                <preferred-size width="150" height="-1"/>
              </grid>
              <forms defaultalign-horz="false"/>
            </constraints>
            <properties/>
          </component>
        </children>
      </grid>
      <grid id="2f13d" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
        <margin top="0" left="0" bottom="0" right="0"/>
        <constraints>
          <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
        </constraints>
        <properties/>
        <border type="none"/>
        <children>
          <component id="abc3c" class="javax.swing.JButton" binding="createButton" default-binding="true">
            <constraints>
              <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
            </constraints>
            <properties>
              <text value="Create"/>
            </properties>
          </component>
          <hspacer id="c4bac">
            <constraints>
              <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
            </constraints>
          </hspacer>
          <vspacer id="d3655">
            <constraints>
              <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
            </constraints>
          </vspacer>
          <component id="abf80" class="javax.swing.JButton" binding="cancelButton" default-binding="true">
            <constraints>
              <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
            </constraints>
            <properties>
              <text value="Cancel"/>
            </properties>
          </component>
        </children>
      </grid>
    </children>
  </grid>
</form>

A client/src/main/java/sh/swisschili/chat/client/AddChannelDialog.java => client/src/main/java/sh/swisschili/chat/client/AddChannelDialog.java +76 -0
@@ 0,0 1,76 @@
package sh.swisschili.chat.client;

import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.uiDesigner.core.Spacer;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

import javax.swing.*;
import java.awt.*;

public class AddChannelDialog extends JDialog {
    private JTextField name;
    private JButton createButton;
    private JButton cancelButton;
    private JPanel panel;

    public interface AddChannelListener {
        void addChannel(String name);
    }

    public AddChannelDialog(AddChannelListener listener) {
        $$$setupUI$$$();

        setContentPane(panel);
        createButton.addActionListener(e -> {
            listener.addChannel(name.getText());
            setVisible(false);
        });
        cancelButton.addActionListener(e -> setVisible(false));
    }

    /**
     * Method generated by IntelliJ IDEA GUI Designer
     * >>> IMPORTANT!! <<<
     * DO NOT edit this method OR call it in your code!
     *
     * @noinspection ALL
     */
    private void $$$setupUI$$$() {
        panel = new JPanel();
        panel.setLayout(new GridLayoutManager(3, 1, new Insets(0, 0, 0, 0), -1, -1));
        final JLabel label1 = new JLabel();
        label1.setText("Create channel");
        panel.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final JPanel panel1 = new JPanel();
        panel1.setLayout(new FormLayout("fill:d:noGrow,left:4dlu:noGrow,fill:d:grow", "center:d:noGrow"));
        panel.add(panel1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final JLabel label2 = new JLabel();
        label2.setText("Name");
        CellConstraints cc = new CellConstraints();
        panel1.add(label2, cc.xy(1, 1));
        name = new JTextField();
        panel1.add(name, cc.xy(3, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JPanel panel2 = new JPanel();
        panel2.setLayout(new GridLayoutManager(2, 3, new Insets(0, 0, 0, 0), -1, -1));
        panel.add(panel2, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        createButton = new JButton();
        createButton.setText("Create");
        panel2.add(createButton, new GridConstraints(1, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final Spacer spacer1 = new Spacer();
        panel2.add(spacer1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
        final Spacer spacer2 = new Spacer();
        panel2.add(spacer2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
        cancelButton = new JButton();
        cancelButton.setText("Cancel");
        panel2.add(cancelButton, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
    }

    /**
     * @noinspection ALL
     */
    public JComponent $$$getRootComponent$$$() {
        return panel;
    }
}

M client/src/main/java/sh/swisschili/chat/client/InfiniteScrollPane.java => client/src/main/java/sh/swisschili/chat/client/InfiniteScrollPane.java +1 -6
@@ 52,12 52,7 @@ public class InfiniteScrollPane<T> extends JScrollPane {

        @Override
        public void afterItemAdded() {
            int min = getVerticalScrollBar().getMinimum();
            int max = getVerticalScrollBar().getMaximum();
            int visible = getVerticalScrollBar().getVisibleAmount();
            int maxPossible = max - visible;

            getVerticalScrollBar().setValue(max);
            SwingUtilities.invokeLater(() -> getVerticalScrollBar().setValue(getVerticalScrollBar().getMaximum()));
        }
    };


M client/src/main/java/sh/swisschili/chat/client/MainWindow.form => client/src/main/java/sh/swisschili/chat/client/MainWindow.form +54 -6
@@ 3,7 3,7 @@
  <grid id="27dc6" binding="panel1" default-binding="true" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
    <margin top="0" left="0" bottom="0" right="0"/>
    <constraints>
      <xy x="20" y="20" width="1164" height="803"/>
      <xy x="20" y="20" width="984" height="560"/>
    </constraints>
    <properties/>
    <border type="none"/>


@@ 70,7 70,9 @@
                  </grid>
                  <scrollpane id="671c2" binding="messagesScrollPane" custom-create="true">
                    <constraints>
                      <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
                      <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false">
                        <minimum-size width="200" height="-1"/>
                      </grid>
                    </constraints>
                    <properties>
                      <horizontalScrollBarPolicy value="31"/>


@@ 105,7 107,7 @@
              </grid>
            </children>
          </splitpane>
          <grid id="a8a05" binding="leftPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
          <grid id="a8a05" binding="leftPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
            <margin top="0" left="0" bottom="0" right="0"/>
            <constraints>
              <splitpane position="left"/>


@@ 123,7 125,7 @@
                <colspec value="left:4dlu:noGrow"/>
                <colspec value="fill:max(d;4px):noGrow"/>
                <constraints>
                  <grid row="1" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
                  <grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
                </constraints>
                <properties/>
                <border type="none"/>


@@ 165,7 167,7 @@
              </grid>
              <scrollpane id="9a078">
                <constraints>
                  <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
                  <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
                </constraints>
                <properties/>
                <border type="none"/>


@@ 178,7 180,7 @@
              </scrollpane>
              <scrollpane id="8c505">
                <constraints>
                  <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
                  <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
                </constraints>
                <properties/>
                <border type="none"/>


@@ 189,6 191,52 @@
                  </component>
                </children>
              </scrollpane>
              <grid id="5f470" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
                <margin top="0" left="0" bottom="0" right="0"/>
                <constraints>
                  <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
                </constraints>
                <properties/>
                <border type="none"/>
                <children>
                  <component id="88b66" class="javax.swing.JButton" binding="joinButton" default-binding="true">
                    <constraints>
                      <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
                    </constraints>
                    <properties>
                      <text value="Join"/>
                    </properties>
                  </component>
                  <hspacer id="c9552">
                    <constraints>
                      <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
                    </constraints>
                  </hspacer>
                </children>
              </grid>
              <grid id="d9a7f" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
                <margin top="0" left="0" bottom="0" right="0"/>
                <constraints>
                  <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
                </constraints>
                <properties/>
                <border type="none"/>
                <children>
                  <component id="99feb" class="javax.swing.JButton" binding="newChannelButton">
                    <constraints>
                      <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
                    </constraints>
                    <properties>
                      <text value="New"/>
                    </properties>
                  </component>
                  <hspacer id="b41f2">
                    <constraints>
                      <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
                    </constraints>
                  </hspacer>
                </children>
              </grid>
            </children>
          </grid>
        </children>

M client/src/main/java/sh/swisschili/chat/client/MainWindow.java => client/src/main/java/sh/swisschili/chat/client/MainWindow.java +35 -5
@@ 55,6 55,8 @@ public class MainWindow {
    private JButton settingsButton;
    private JPanel userPanel;
    private JComboBox<String> statusBox;
    private JButton joinButton;
    private JButton newChannelButton;

    private JFrame frame;



@@ 167,6 169,18 @@ public class MainWindow {
        settingsButton.addActionListener(e -> {
            new SettingsDialog(frame).setVisible(true);
        });

        joinButton.addActionListener(e ->
                new AddGroupDialog(this::groupAdded)
                        .setVisible(true));


        newChannelButton.addActionListener(e -> {
            ServerGroup group = groups.getSelectedValue();
            if (group == null)
                return;
            new AddChannelDialog(group::createChannel).setVisible(true);
        });
    }

    private void updateStatus(UserStatus status) {


@@ 335,7 349,7 @@ public class MainWindow {
        sendButton.setText("Send");
        panel3.add(sendButton, cc.xy(3, 1));
        messagesScrollPane.setHorizontalScrollBarPolicy(31);
        panel2.add(messagesScrollPane, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
        panel2.add(messagesScrollPane, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, new Dimension(200, -1), null, null, 0, false));
        messagesScrollPane.setViewportView(messages);
        final JPanel panel4 = new JPanel();
        panel4.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));


@@ 343,11 357,11 @@ public class MainWindow {
        users = new JList();
        panel4.add(users, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(150, 50), null, 0, false));
        leftPanel = new JPanel();
        leftPanel.setLayout(new GridLayoutManager(2, 2, new Insets(0, 0, 0, 0), -1, -1));
        leftPanel.setLayout(new GridLayoutManager(3, 2, new Insets(0, 0, 0, 0), -1, -1));
        splitPane1.setLeftComponent(leftPanel);
        final JPanel panel5 = new JPanel();
        panel5.setLayout(new FormLayout("fill:max(d;4px):noGrow,left:4dlu:noGrow,fill:d:grow,left:4dlu:noGrow,fill:max(d;4px):noGrow", "center:d:grow,top:4dlu:noGrow,center:max(d;4px):noGrow"));
        leftPanel.add(panel5, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        leftPanel.add(panel5, new GridConstraints(2, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        settingsButton = new JButton();
        settingsButton.setText("Settings");
        panel5.add(settingsButton, cc.xy(5, 1));


@@ 358,13 372,29 @@ public class MainWindow {
        final Spacer spacer1 = new Spacer();
        panel5.add(spacer1, cc.xy(3, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        final JScrollPane scrollPane1 = new JScrollPane();
        leftPanel.add(scrollPane1, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
        leftPanel.add(scrollPane1, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
        channels = new JList();
        scrollPane1.setViewportView(channels);
        final JScrollPane scrollPane2 = new JScrollPane();
        leftPanel.add(scrollPane2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
        leftPanel.add(scrollPane2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
        groups = new JList();
        scrollPane2.setViewportView(groups);
        final JPanel panel6 = new JPanel();
        panel6.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1));
        leftPanel.add(panel6, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        joinButton = new JButton();
        joinButton.setText("Join");
        panel6.add(joinButton, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final Spacer spacer2 = new Spacer();
        panel6.add(spacer2, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
        final JPanel panel7 = new JPanel();
        panel7.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1));
        leftPanel.add(panel7, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        newChannelButton = new JButton();
        newChannelButton.setText("New");
        panel7.add(newChannelButton, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final Spacer spacer3 = new Spacer();
        panel7.add(spacer3, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
    }

    /**

M client/src/main/java/sh/swisschili/chat/client/ServerGroup.java => client/src/main/java/sh/swisschili/chat/client/ServerGroup.java +28 -0
@@ 18,6 18,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.

package sh.swisschili.chat.client;

import io.grpc.Server;
import io.grpc.stub.StreamObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@@ 175,6 176,33 @@ public class ServerGroup {
                        });
    }

    public void createChannel(String name) {
        ChatGrpc.ChatStub stub = pool.chatStubFor(server);

        ServerGroup g = this;

        stub.createChannel(CreateChannelRequest.newBuilder().setGroup(group)
                        .setChannelName(name).build(),
                new StreamObserver<CreateChannelResponse>() {
                    @Override
                    public void onNext(CreateChannelResponse value) {
                        ServerChannel channel = new ServerChannel(pool, server, value.getChannel(), authorizedUser);
                        g.channels.add(channel);
                        g.model.addElement(channel);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onCompleted() {
                        // nothing
                    }
                });
    }

    public DefaultListModel<ServerChannel> getModel() {
        return model;
    }

M server/src/main/java/sh/swisschili/chat/server/ChatService.java => server/src/main/java/sh/swisschili/chat/server/ChatService.java +2 -0
@@ 27,6 27,8 @@ import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sh.swisschili.chat.util.*;
import sh.swisschili.chat.util.ChatGrpc;
import sh.swisschili.chat.util.ChatProtos;

import java.io.IOException;
import java.security.PublicKey;