$ git clone http://tcclient.ion.nu/tc_client.git
commit 2b70531be90c06305cde01bab209e86403c8f137
Author: Alicia <...>
Date:   Wed Sep 14 18:23:00 2016 +0200

    tc_client-gtk: break the cameras into two or more rows if it means they can be larger and still fit.

diff --git a/ChangeLog b/ChangeLog
index a87c1af..d28a420 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -19,6 +19,7 @@ tc_client-gtk: when automatically opening cameras is disabled don't open the one
 tc_client-gtk: added menu item to hide a camera upon right-click.
 tc_client-gtk: limit the camera preview to 640 by 480, scaling down if the input is larger.
 tc_client-gtk: when resizing the window, resize the camera pane before the chat pane.
+tc_client-gtk: break the cameras into two or more rows if it means they can be larger and still fit.
 tc_client-gtk and camviewer: fixed compatibility with newer libavutil.
 tc_client-gtk and camviewer: added compatibility fallbacks for av_image_get_buffer_size() and av_packet_unref()
 tc_client-gtk and camviewer: specify a scaling algorithm for libswscale (mandatory for older versions of libswscale)
diff --git a/README b/README
index 8136c3b..d501438 100644
--- a/README
+++ b/README
@@ -21,6 +21,11 @@ Commands supported by tc_client:
 /topic <topic>  = set the channel topic
 /help           = list these commands at runtime
 
+Missing features:
+broadcasting support for channels which require a password to broadcast
+broadcasting support for channels where mods need to approve streams
+support for reviewing and approving streams as a mod
+
 Current commands sent by the TC servers that tc_client doesn't know how to handle:
 notice (some, notice is used for many functions)
 joinsdone
diff --git a/gtkgui.glade b/gtkgui.glade
index b02ee9e..dc2df3f 100644
--- a/gtkgui.glade
+++ b/gtkgui.glade
@@ -1100,6 +1100,7 @@
                       <object class="GtkBox" id="cambox">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
+                        <property name="orientation">vertical</property>
                         <child>
                           <placeholder/>
                         </child>
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index d9d0713..19a8e0e 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -58,7 +58,6 @@
 
 struct viddata
 {
-  GtkWidget* box;
   AVCodec* vdecoder;
   AVCodec* vencoder;
   AVCodec* adecoder;
@@ -85,43 +84,6 @@ char frombuild=0; // Running from the build directory
   PROCESS_INFORMATION coreprocess={.hProcess=0};
 #endif
 
-void updatescaling(struct viddata* data, unsigned int width, unsigned int height)
-{
-// TODO: Move updatescaling into media.c?
-  if(!camcount){return;}
-  if(!width){width=gtk_widget_get_allocated_width(data->box);}
-  if(!height){height=gtk_widget_get_allocated_height(data->box);}
-  camsize_scale.width=width/camcount;
-  // 3/4 ratio
-  camsize_scale.height=camsize_scale.width*3/4;
-  unsigned int i;
-  unsigned int labelsize=0;
-  for(i=0; i<camcount; ++i)
-  {
-    if(gtk_widget_get_allocated_height(cams[i].label)>labelsize)
-      labelsize=gtk_widget_get_allocated_height(cams[i].label);
-  }
-  // Fit by height
-  if(height<camsize_scale.height+labelsize)
-  {
-    camsize_scale.height=height-labelsize;
-    camsize_scale.width=camsize_scale.height*4/3;
-  }
-  if(camsize_scale.width<8){camsize_scale.width=8;}
-  if(camsize_scale.height<1){camsize_scale.height=1;}
-  // TODO: wrapping and stuff
-  // Rescale current images to fit
-  for(i=0; i<camcount; ++i)
-  {
-    GdkPixbuf* pixbuf=gtk_image_get_pixbuf(GTK_IMAGE(cams[i].cam));
-    if(!pixbuf){continue;}
-    GdkPixbuf* old=pixbuf;
-    pixbuf=gdk_pixbuf_scale_simple(pixbuf, camsize_scale.width, camsize_scale.height, GDK_INTERP_BILINEAR);
-    gtk_image_set_from_pixbuf(GTK_IMAGE(cams[i].cam), pixbuf);
-    g_object_unref(old);
-  }
-}
-
 void printchat(const char* text, const char* pm)
 {
   GtkAdjustment* scroll;
@@ -473,11 +435,8 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
     avcodec_open2(cam->actx, data->adecoder, 0);
     cam->samples=0;
 #endif
-    gtk_box_pack_start(GTK_BOX(data->box), cam->box, 0, 0, 0);
+    updatescaling(0, 0, 1);
     gtk_widget_show_all(cam->box);
-    updatescaling(data, 0, 0);
-    while(gtk_events_pending()){gtk_main_iteration();} // Make sure the label gets its size before we calculate scaling
-    updatescaling(data, 0, 0);
     return 1;
   }
   if(!strcmp(buf, "Starting outgoing media stream"))
@@ -496,17 +455,13 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
     cam->dstframe->data[0]=0;
 
     cam->actx=0;
-    gtk_box_pack_start(GTK_BOX(data->box), cam->box, 0, 0, 0);
+    updatescaling(0, 0, 1);
     gtk_widget_show_all(cam->box);
-    updatescaling(data, 0, 0);
-    while(gtk_events_pending()){gtk_main_iteration();} // Make sure the label gets its size before we calculate scaling
-    updatescaling(data, 0, 0);
     return 1;
   }
   if(!strncmp(buf, "VideoEnd: ", 10))
   {
     camera_remove(&buf[10]);
-    updatescaling(data, 0, 0);
     return 1;
   }
   if(!strncmp(buf, "Audio: ", 7))
@@ -644,9 +599,9 @@ void togglecam(GtkCheckMenuItem* item, struct viddata* data)
 gboolean handleresize(GtkWidget* widget, GdkEventConfigure* event, struct viddata* data)
 {
   char bottom=autoscroll_before(data->scroll);
-  if(event->width!=gtk_widget_get_allocated_width(data->box))
+  if(event->width!=gtk_widget_get_allocated_width(cambox))
   {
-    updatescaling(data, event->width, 0);
+    updatescaling(event->width, 0, 0);
   }
 #ifndef _WIN32 // For some reason scrolling as a response to resizing freezes windows
   if(bottom){autoscroll_after(data->scroll);}
@@ -657,7 +612,7 @@ gboolean handleresize(GtkWidget* widget, GdkEventConfigure* event, struct viddat
 void handleresizepane(GObject* obj, GParamSpec* spec, struct viddata* data)
 {
   char bottom=autoscroll_before(data->scroll);
-  updatescaling(data, 0, gtk_paned_get_position(GTK_PANED(obj)));
+  updatescaling(0, gtk_paned_get_position(GTK_PANED(obj)), 0);
 #ifndef _WIN32
   if(bottom){autoscroll_after(data->scroll);}
 #endif
@@ -1002,7 +957,7 @@ int main(int argc, char** argv)
   item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_options_settings2"));
   g_signal_connect(item, "activate", G_CALLBACK(showsettings), gui);
   
-  data->box=GTK_WIDGET(gtk_builder_get_object(gui, "cambox"));
+  cambox=GTK_WIDGET(gtk_builder_get_object(gui, "cambox"));
   userlistwidget=GTK_WIDGET(gtk_builder_get_object(gui, "userlistbox"));
   GtkWidget* chatview=GTK_WIDGET(gtk_builder_get_object(gui, "chatview"));
   data->scroll=gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gtk_builder_get_object(gui, "chatscroll")));
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
index e589b41..3ed5b65 100644
--- a/utilities/gtk/media.c
+++ b/utilities/gtk/media.c
@@ -50,6 +50,9 @@ unsigned int camcount=0;
 #endif
 struct size camsize_out={.width=320, .height=240};
 struct size camsize_scale={.width=320, .height=240};
+GtkWidget* cambox;
+GtkWidget** camrows=0;
+unsigned int camrowcount=0;
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
 // Experimental mixer, not sure if it really works
@@ -102,6 +105,7 @@ void camera_remove(const char* id)
       break;
     }
   }
+  updatescaling(0, 0, 1);
 }
 
 void camera_removebynick(const char* nick)
@@ -124,6 +128,7 @@ void camera_removebynick(const char* nick)
       break;
     }
   }
+  updatescaling(0, 0, 1);
 }
 
 struct camera* camera_find(const char* id)
@@ -292,7 +297,7 @@ GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay)
   int campipe[2];
 #ifndef _WIN32
   CAM* cam=cam_open(name); // Opening here in case of GUI callbacks
-  if(cam){cam_resolution(cam, &camsize_out.width, &camsize_out.height);}
+  if(cam){cam_resolution(cam, (unsigned int*)&camsize_out.width, (unsigned int*)&camsize_out.height);}
   pipe(campipe);
   camproc=fork();
   if(!camproc)
@@ -453,3 +458,79 @@ void camera_postproc(struct camera* cam, unsigned char* buf, unsigned int width,
     }
   }
 }
+
+void updatescaling(unsigned int width, unsigned int height, char changedcams)
+{
+  if(!camcount){return;}
+  if(!width){width=gtk_widget_get_allocated_width(GTK_WIDGET(gtk_builder_get_object(gui, "main")));}
+  if(!height){height=gtk_widget_get_allocated_height(GTK_WIDGET(gtk_builder_get_object(gui, "camerascroll")));}
+
+  GtkRequisition label;
+  gtk_widget_get_preferred_size(cams[0].label, &label, 0);
+  camsize_scale.width=1;
+  camsize_scale.height=1;
+  unsigned int rowcount=1;
+  unsigned int rows;
+  for(rows=1; rows<=camcount; ++rows)
+  {
+    struct size scale;
+    unsigned int cams_per_row=camcount/rows;
+    if(camcount%rows){++cams_per_row;}
+    scale.width=width/cams_per_row;
+    // 3/4 ratio
+    scale.height=scale.width*3/4;
+    unsigned int rowheight=height/rows;
+    // Fit by height
+    if(rowheight<scale.height+label.height)
+    {
+      scale.height=rowheight-label.height;
+      scale.width=scale.height*4/3;
+    }
+    if(scale.width>camsize_scale.width) // Check if this number of rows will fit larger cams
+    {
+      camsize_scale.width=scale.width;
+      camsize_scale.height=scale.height;
+      rowcount=rows;
+    }else if(scale.width<camsize_scale.width){break;} // Only getting smaller from here, use the last one that increased
+  }
+
+  unsigned int i;
+  if(rowcount!=camrowcount || changedcams) // Changed the number of rows, shuffle everything around to fit. Or added/removed a camera, in which case we need to shuffle things around anyway
+  {
+    for(i=0; i<camcount; ++i)
+    {
+      g_object_ref(cams[i].box); // Increase reference counts so that they are not deallocated while they are temporarily detached from the rows
+      gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(cams[i].box)), cams[i].box);
+    }
+    for(i=0; i<camrowcount; ++i){gtk_widget_destroy(camrows[i]);} // Erase old rows
+    camrowcount=rowcount;
+    camrows=realloc(camrows, sizeof(GtkWidget*)*camrowcount);
+    for(i=0; i<camrowcount; ++i)
+    {
+      camrows[i]=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+      gtk_box_pack_start(GTK_BOX(cambox), camrows[i], 1, 0, 0);
+      gtk_widget_set_halign(camrows[i], GTK_ALIGN_CENTER);
+      gtk_widget_show(camrows[i]);
+    }
+    unsigned int cams_per_row=camcount/camrowcount;
+    if(camcount%camrowcount){++cams_per_row;}
+    for(i=0; i<camcount; ++i)
+    {
+      gtk_box_pack_start(GTK_BOX(camrows[i/cams_per_row]), cams[i].box, 0, 0, 0);
+      g_object_unref(cams[i].box); // Decrease reference counts once they're attached again
+    }
+  }
+  // libswscale doesn't handle unreasonably small sizes well
+  if(camsize_scale.width<8){camsize_scale.width=8;}
+  if(camsize_scale.height<1){camsize_scale.height=1;}
+  // Rescale current images to fit
+  for(i=0; i<camcount; ++i)
+  {
+    GdkPixbuf* pixbuf=gtk_image_get_pixbuf(GTK_IMAGE(cams[i].cam));
+    if(!pixbuf){continue;}
+    GdkPixbuf* old=pixbuf;
+    pixbuf=gdk_pixbuf_scale_simple(pixbuf, camsize_scale.width, camsize_scale.height, GDK_INTERP_BILINEAR);
+    gtk_image_set_from_pixbuf(GTK_IMAGE(cams[i].cam), pixbuf);
+    g_object_unref(old);
+  }
+}
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
index 3a0a701..d2bf77d 100644
--- a/utilities/gtk/media.h
+++ b/utilities/gtk/media.h
@@ -39,8 +39,8 @@ struct camera
 };
 struct size
 {
-  unsigned int width;
-  unsigned int height;
+  int width;
+  int height;
 };
 extern struct camera campreview;
 extern struct camera* cams;
@@ -53,6 +53,7 @@ extern unsigned int camcount;
 #endif
 extern struct size camsize_out;
 extern struct size camsize_scale;
+extern GtkWidget* cambox;
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
 extern void camera_playsnd(int audiopipe, struct camera* cam, short* samples, unsigned int samplecount);
@@ -71,3 +72,4 @@ extern gboolean camselect_cancel(GtkWidget* widget, void* x1, void* x2);
 extern void camselect_accept(GtkWidget* widget, AVCodec* vencoder);
 extern const char* camselect_file(void);
 extern void camera_postproc(struct camera* cam, unsigned char* buf, unsigned int width, unsigned int height);
+extern void updatescaling(unsigned int width, unsigned int height, char changedcams);