From 943d575ec3a99a5715de8552006ec2079e17c85a Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 29 Sep 2013 13:43:27 -0400 Subject: [PATCH] Add GtkFlowBox GtkFlowBox is a container that its children in a reflowing grid, which can be oriented horizontally or vertically. It is similar to GtkListBox in that the children can be sorted and filtered, and by requiring a dedicated child widget type, GtkFlowBoxChild. It is similar to GtkTreeView in that is supports a full set of selection modes, including rubberband selection. This is the culmination of work that has happened in the egg-list-box module, and earlier in libegg. The origins of this code are the EggSpreadTable in libegg, which was written by Tristan van Berkom. It was moved to egg-list-box and renamed EggFlowBox by Jon McCann, and I gave it some finishing touched in the flowbox-improvements branch of that module. --- docs/reference/gtk/Makefile.am | 3 +- docs/reference/gtk/gtk-docs.sgml | 1 + docs/reference/gtk/gtk3-sections.txt | 49 + docs/reference/gtk/gtk3.types.in | 2 + docs/reference/gtk/images/flow-box.png | Bin 0 -> 6359 bytes docs/reference/gtk/visual_index.xml | 3 + docs/tools/widgets.c | 44 + gtk/Makefile.am | 2 + gtk/a11y/Makefile.am | 2 + gtk/a11y/gtkflowboxaccessible.c | 254 ++ gtk/a11y/gtkflowboxaccessible.h | 58 + gtk/a11y/gtkflowboxaccessibleprivate.h | 30 + gtk/a11y/gtkflowboxchildaccessible.c | 70 + gtk/a11y/gtkflowboxchildaccessible.h | 55 + gtk/gtk-a11y.h | 2 + gtk/gtk.h | 1 + gtk/gtkflowbox.c | 4719 ++++++++++++++++++++++++ gtk/gtkflowbox.h | 220 ++ tests/Makefile.am | 5 + tests/testflowbox.c | 692 ++++ 20 files changed, 6211 insertions(+), 1 deletion(-) create mode 100644 docs/reference/gtk/images/flow-box.png create mode 100644 gtk/a11y/gtkflowboxaccessible.c create mode 100644 gtk/a11y/gtkflowboxaccessible.h create mode 100644 gtk/a11y/gtkflowboxaccessibleprivate.h create mode 100644 gtk/a11y/gtkflowboxchildaccessible.c create mode 100644 gtk/a11y/gtkflowboxchildaccessible.h create mode 100644 gtk/gtkflowbox.c create mode 100644 gtk/gtkflowbox.h create mode 100644 tests/testflowbox.c diff --git a/docs/reference/gtk/Makefile.am b/docs/reference/gtk/Makefile.am index 1c60e59693..c1afc06c55 100644 --- a/docs/reference/gtk/Makefile.am +++ b/docs/reference/gtk/Makefile.am @@ -421,7 +421,8 @@ HTML_IMAGES = \ $(srcdir)/images/getting-started-app7.png \ $(srcdir)/images/getting-started-app8.png \ $(srcdir)/images/getting-started-app9.png \ - $(srcdir)/images/exampleapp.png + $(srcdir)/images/exampleapp.png \ + $(srcdir)/images/flow-box.png # Extra options to supply to gtkdoc-fixref FIXXREF_OPTIONS=--extra-dir=../gdk/html \ diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml index 9099510826..35008048a8 100644 --- a/docs/reference/gtk/gtk-docs.sgml +++ b/docs/reference/gtk/gtk-docs.sgml @@ -72,6 +72,7 @@ + diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index 27bb480c3b..88c251e4ab 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -7737,3 +7737,52 @@ GtkRevealerTransitionType gtk_revealer_get_transition_type gtk_revealer_set_transition_type + +
+gtkflowbox +GtkFlowBox +GtkFlowBox +gtk_flow_box_new +gtk_flow_box_insert +gtk_flow_box_get_child_at_index +gtk_flow_box_set_hadjustment +gtk_flow_box_set_vadjustment + +gtk_flow_box_set_homogeneous +gtk_flow_box_get_homogeneous +gtk_flow_box_set_row_spacing +gtk_flow_box_get_row_spacing +gtk_flow_box_set_column_spacing +gtk_flow_box_get_column_spacing +gtk_flow_box_set_min_children_per_line +gtk_flow_box_get_min_children_per_line +gtk_flow_box_set_max_children_per_line +gtk_flow_box_get_max_children_per_line +gtk_flow_box_set_activate_on_single_click +gtk_flow_box_get_activate_on_single_click + +GtkFlowBoxForeachFunc +gtk_flow_box_selected_foreach +gtk_flow_box_get_selected_children +gtk_flow_box_select_child +gtk_flow_box_unselect_child +gtk_flow_box_select_all +gtk_flow_box_unselect_all +gtk_flow_box_set_selection_mode +gtk_flow_box_get_selection_mode + +GtkFlowBoxFilterFunc +gtk_flow_box_set_filter_func +gtk_flow_box_invalidate_filter + +GtkFlowBoxSortFunc +gtk_flow_box_set_sort_func +gtk_flow_box_invalidate_sort + + +GtkFlowBoxChild +gtk_flow_box_child_new +gtk_flow_box_child_get_index +gtk_flow_box_child_is_selected +gtk_flow_box_child_changed +
diff --git a/docs/reference/gtk/gtk3.types.in b/docs/reference/gtk/gtk3.types.in index 2d8b44a704..c20b83f9e4 100644 --- a/docs/reference/gtk/gtk3.types.in +++ b/docs/reference/gtk/gtk3.types.in @@ -70,6 +70,8 @@ gtk_file_chooser_get_type gtk_file_chooser_widget_get_type gtk_file_filter_get_type gtk_fixed_get_type +gtk_flow_box_get_type +gtk_flow_box_child_get_type gtk_font_button_get_type gtk_font_chooser_get_type gtk_font_chooser_dialog_get_type diff --git a/docs/reference/gtk/images/flow-box.png b/docs/reference/gtk/images/flow-box.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef99d3a052e14097a9a0dd831d2cc0f8288d292 GIT binary patch literal 6359 zcma)>bx<77w#Nqxiw4)=gy0_Bf;%A)AV7d%fn5k5U~vf=Jh+ApERw~7YhZEB;ua(X z5AG6pdw=)6e_qvn^=fLSYpT1tPoL>?xm8sz;efz`K-usIfpm2gPZ%+)8C?f?rl}0Ib7|0mc zkz#d+IgZtw;fsEFd6;O7t5>2bX$A2(XV=uT|7jFCm8RY|9$)7zNs^oL=?xd6*D*zP zA%hxomBjfppah}z@4ny&XW7EsU=kyqvhwvQIz%ySXuSQ<*I(XJUY<#w5cxyodycaY@q_Jyc6-zfW?-A=vuxspiN~w{v%FiDufLOvAUdO0~c%(;*?vQzs z#Yy3gJK%=Bp$V@NsXYS#&}Uc}G+1;8zo33eP94Ue2OsBrkjNCUXf@xiTc{5NuaWIZ z#ECsE)#6XH;BL{@{{)AtebT04PgMk^a#QB_t+_L0{3v!beQ7DMLSVvMs9Q=AtIVz; zHuS?vP_%T)Iw`8B#$De_({8lMTtz1?JKN0m{_cgNm9#~6*z5&NM@Ofph*}^uRi9#g zdzp+Wa%k#dY`hV4&LMbg)~z`TaH-9oZu9!q*&+`#d=|7P%!PvMsZz z?dFBNX(Ok&Ku2*shX~J2Gd%XAUsjJ;fI|tVil!Mb1Y53vR5&4O{h3oIj zBdMaAnlf%#k(1x01~#g3&G=E5aOL z6;j_5>}~A?%&Og$kEJs64Gn%J~ZI00!F%vDHkeQ$3Y?o8*oPs#Nd^#==QGXBazt_j(Pl2j~I zRh!LJFb!uz-rLYbT>1EAuWflY%j@QIRhEDV3DtEqSwWb6f}fxFTbpqq%s=;!49z%9 z62<8ljW{nSK{e%7tR}_6!QHQP@r{?><(aMW3KW`vdvxu9U>t*>=8}uARgjelVq&qnVn=Uc` z{G5}*MlGRcVl=S9Bi)3TnDB79&QH%Guru;t#9(Sh;M#iNw#j2SwI}iY&Z+VJtM%3W z{ngdLw$?&BBjXB-@=`?Z?dZZs1zJHuzoVwN-`w3tH#aY~e?m~yTI$K&g2M|#yBi2I z^cY#fzGT44>_7^SalqceLFL3Q*G>?`i2orW$j9fY8&f$QB5i0Lzo$`1AX~fGK568> zVQ-d;Dz*p%LtAAeUtF#91>fy$?8WBj{&GX5qzmw!2|mVaSvQaT`4fp;gZehv8I2HN z6|AfIUg4D9JiKl7emJm76W6Op70_!LK-~KM#2Xr_SBn-732mqHp zej+R%k!%0_dA0my zIc^`}weLY*u35uyzVzFJ_Q}Oo{~@Ol34QqlQy6O!2q-|!`We08r4m6n&l-FlR$m6! z#@g9|>#o<+Eovr`>>}>@UXF&lHhK58E?HO%GdF0NWT0%p;1|@C)R0#Ty=F#-kcb!qph*8DN+7_`g#*<75hLw>9qZ+#2`E7AH#(PznQr$4RZtJz@O#PZ z7blXfN-2OuOV`dF_~A2`w|A*sxGkTx;os`diGkpu$9!JaJPSAD5hRN0`jb^(y1)K$ ziZja2OHGwvmt&EWW8*O^dO=A=MWmxy;F#scsu&WiVJ)1=Ze6WeRFl<4exb-zr+Vdo zhh92Q#>d^qHYIcM3{r-3qME$zCQ>@B39AERzKV z$phb8Ux}cX#Lz9mq`7+=bD?j?dtlu?-&bl~d7f$WHa_IpkY@o;Bcg6mB6 z4agr+*urOtQ5YV2Da*b$if>T31ablAnMo*peSbzheFKB{SI5JS24*@#+OIY#DO@FT zqU73&LPB=Mow9^*F3!)ndAVz{Y&Ryk4D3hl999eQD%LM;88{C9q9pz~t`4-Wqt*}HE$iBC^|tM69L z2Ll$#CPv~)EBLi*;&E^pcgxIRQ1~nd?IjN zO8gIgfwxJ_QAu2EUoAe7pG{!Ud8Me=zniae?i)S*`c%J_a`JtI8Jq9=Sy?8 zag*={a5JynjUjF>1u#0WG~8)uF>sh)`(d{z_&Lrlx>&L zSIds}6MMv;sc(@CN={;ab8lnO@qdr%QW4c@X=&*=z4h4ptWSvKl@3ii^le3Ci}^Qr z%*=fpMykaN7sVtKJ%@;HSfNt z+JrZmJu`_je{5#=j3Xr)xhg6qronFAW|8oQMk5*8#)$d-vBsmmI#a}yzL-Wb_A^oX z$(lJQ;z*<|0kGmK;Uu=;KlmwyT>~~?D@up+uWk!C%j`DH`5OvKm}`RAJ?2$kdmV~A z#tczlCXx7rjT9WpMiYVsWXJZqTkET?`tSDN=A7zbrSru0Getr%txtsGi##(*eICG2 z4(-`sVzgoG144T0j-qtEk#qTm)z=Uq8o~!*o&JLqrLQDul_VnO2JWjJcS%R8rl!q) zEjBFhrg~)FGQ^I!Rv^^q_Iz>}59Qu7%U_#>zM)ak&$UHFP3eNmnd9Ee*_ooogx=7;7{K@*gaU?U)}1*bF2;f%sDtTG&D9gMoDE{$0Mqft<6!+ z)9@!ubpFU>>9Jz%Z@l|G);!=}^r36Ea2+iTS*UxI8<$JoCe;i|AnH%}zZnJhCxKT6 zbA|AHTNZ&RP3`faOt&ioNuI&=(qxnDb&YHibDWeaNvZa6Go?7^d>=Y%(K*BvzGvX$ zzl9MZO)wp);}veV2(H3cTUBw`m!yMdkm*`0Cb{#5u1X$Sj^71WdTkuzYDX`F6FP;M zSF0x<#1w`|=Q~X5Lk&*Uppw`e%+ab0c7+E)qNer%b#CW%90-hh(aEKLW%0=F?#s(d zOyDcO;=K3uU_ANEvJaP3o3h(pFQ0CP$jvn?ubV2220%iimvs&|JPtKeQOZg#q!8^- zec5IeZm%vYypi=eh$~4;$INcC?xpK#gYpDp>gDlw0lTGkk>%s}V5c-^+me-(P0Zqh zibf-kpuKY~tbMRy_v2Y?BY1nu_IJM2H>)Qhd_;BP;nnD4{=_s{QyA@x_`T6}Z>+wT z&V#rmtEa?W`Z*{EZR*t=iVpcfI4Jb-AscF zcMh+%C$V6ja3%Z{g2|=~fSi1Oux)aS-I-ob$LhPM%H6!EyEUpax&C3n`XkfL1V;0K zT|fU7@@T5Oix$=+vGj74Ucu?1?H!D@DVX_hcpc2-$ zv+Uje&d;7Nd}WVi31+*${AB>4O70fzi1gP>efm|FE0U!9J!#-^jt%|jf^e<8Wz)?f z116RXal2FPDcpZOe%O>M#5sdRn57qTmBh`Wp-sCpA>8^hcGyphP<3s@)N6%dBwL_g z?B3K$FpEP@0P-VCSnGGHcscx*ja{xQ=)mr`5)&~ox=^aZ zDjz>U%mzKBM&d0VoCTfE?C4054>x~l3@G3E`Kh2g@XlXu*|w1RC|p1-P*mb8ir(u1 zpm1+>H=sp?P_3U}0stSpS3odFuETh57MWmQhV(ryN3RInk-^M7;q#YW)nf{r~E?H&#-3esG#5)#}*uz@( zZeUp*Uwwy~nF2qP!6xrgmVuFWbXe9|r!qS6n5u)ur}m3nr6S@fIuwk&E%zhjDGYx zUUi<(o36L4>;S-qbHXF)Pv+uv(*Wi3+Hqg&chykzMzt(d3P_nq`h_37g?*s`ic;qz z_n{vt%ss2zcLekGEJ58u1>?^TDPP{ThhTSZxb`d4N*0t;hoiv{ z|DX+&P^f*J1#AuJG#-X5~!QA8%PELA+r4{HX$1iJzm7 z(<3Vdh(ljXmJ{Y$%~3B^fTh`Lz3as5*d%iuO`4C(ep+am2S8^TAh`GqR8`j=5yVgl zNBIB9l0NJ|r!(GleCn!gMgkOVgbFomcDrCx6#Z{xCwot)&$p03$ z{)0%Sn|;AVH0(0t?kN^uPN^t%!W z0*1iHGB1$rlL?z0%ewV>f>L#=3!(q;yMFo8fAc#UPN*m+5*dF~GLe6-DxpDEx38`a zJL>+!``=p5KCu3yXxv;~o62=9k1KEY#MXK5{Ia&m;M1*qEzse+JN;dqHviyoXgME1 z_pif|V&vz2&qauAT--2AwO#+sdj-ZxM{_Z@_`9V?^6!U#H{5&4$;*4)hhWL?Kh}sx zIo$f7tf^uz9mn2P00$4)EP{&?W@QL;qaB`ZCs-OFjG2M$+=d#GVE;xxdu* zJXJdwL%Q9f8HdLxh(WU89*t0F29fZ-yG?BDyuzWL1V04|PJb1(ldf*lX3|7F?l#*V z${XpI$xa%0?p&3xhtX)$_Wi{{;>wE1_O{Zm2l={s3DNQGuV49! zvB6xFFSk!mIWx6ctA*|LX!B(3#@Wg3$`EZS%|Z9K3Di#J4fVzK(`C_UP&+g3%^wrE zwCaR^8mNW#xF@Mkj;5|Qd+uE6TUzGNJ`2l`_qZR|&V7LgxO%kQ?BEb0yIHGWlwt#f zTAIVdgVW%;?aWsicuHgu-SD^er8~O>1hEm1ADh;xB!5w6JwH9%LbWZlzWN3VmGIj5 z$UrU;pMahh3`K+HWYQ&g0QbsKfdOQaYS>6;{5x8nQ0XD!cn(8NPEJ3-#lZj~I5-?? z=UqA$DyG0_+9Utl2VHj7U5lfKgL;46?z*2r z5ICP8KxS*wIY29eO1`on7`b{H3pya9st;T%zStNh!}}oJzN|#j{cY?cHh`^Bgbs#4 zxkD{&&)VmlThn#rl?h`ZVUfRZ`|zbIAp%G(1De?|_dGXkiPn<4Ji(26H z_%uhzZ=e1^yV?D0Fs4YWwQYYt1X;U{b|hNdj}Qvm{~SF{4+Rt~o^=)qr1`gd&Mi&m zhkh9+f^?i8A9uSgmPMmQAKhF@k)VAiaq$MOlqXl$Xbkzo^Eb@9N9y6$6>38PtvaVF z^KwMBNXKlSLBmj-WcYheZlz@~$rA+rQSa-{{+|;5e@J2f==@hfN!oEAWWXM-=sy*J NhKjCog`!pHe*yokV(0(> literal 0 HcmV?d00001 diff --git a/docs/reference/gtk/visual_index.xml b/docs/reference/gtk/visual_index.xml index 2a64ad9c58..1df14dda56 100644 --- a/docs/reference/gtk/visual_index.xml +++ b/docs/reference/gtk/visual_index.xml @@ -126,6 +126,9 @@ + + + diff --git a/docs/tools/widgets.c b/docs/tools/widgets.c index 04af08b4fc..76bd20e2ad 100644 --- a/docs/tools/widgets.c +++ b/docs/tools/widgets.c @@ -1430,12 +1430,56 @@ create_list_box (void) return info; } +static WidgetInfo * +create_flow_box (void) +{ + GtkWidget *widget; + GtkWidget *box; + GtkWidget *vbox; + GtkWidget *child; + GtkWidget *button; + WidgetInfo *info; + + widget = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_IN); + + box = gtk_flow_box_new (); + gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (box), 2); + gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (box), 2); + gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (box), GTK_SELECTION_BROWSE); + button = gtk_label_new ("Child One"); + gtk_container_add (GTK_CONTAINER (box), button); + button = gtk_button_new_with_label ("Child Two"); + gtk_container_add (GTK_CONTAINER (box), button); + child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_add (GTK_CONTAINER (child), gtk_label_new ("Child Three")); + button = gtk_check_button_new (); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + gtk_container_add (GTK_CONTAINER (child), button); + gtk_container_add (GTK_CONTAINER (box), child); + gtk_flow_box_select_child (GTK_FLOW_BOX (box), + GTK_FLOW_BOX_CHILD (gtk_widget_get_parent (child))); + + gtk_container_add (GTK_CONTAINER (widget), box); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), gtk_label_new ("Flow Box"), + FALSE, FALSE, 0); + info = new_widget_info ("flow-box", vbox, ASIS); + info->no_focus = FALSE; + + return info; +} + GList * get_all_widgets (void) { GList *retval = NULL; retval = g_list_prepend (retval, create_list_box()); + retval = g_list_prepend (retval, create_flow_box()); retval = g_list_prepend (retval, create_headerbar ()); retval = g_list_prepend (retval, create_placessidebar ()); retval = g_list_prepend (retval, create_stack ()); diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 8cfb620c6f..b4ec377fac 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -249,6 +249,7 @@ gtk_public_h_sources = \ gtkfilechooserwidget.h \ gtkfilefilter.h \ gtkfixed.h \ + gtkflowbox.h \ gtkfontbutton.h \ gtkfontchooser.h \ gtkfontchooserdialog.h \ @@ -741,6 +742,7 @@ gtk_base_c_sources = \ gtkfilesystem.c \ gtkfilesystemmodel.c \ gtkfixed.c \ + gtkflowbox.c \ gtkfontbutton.c \ gtkfontchooser.c \ gtkfontchooserdialog.c \ diff --git a/gtk/a11y/Makefile.am b/gtk/a11y/Makefile.am index 25205b3f30..2be4c99ca9 100644 --- a/gtk/a11y/Makefile.am +++ b/gtk/a11y/Makefile.am @@ -18,6 +18,8 @@ gtka11y_c_sources = \ gtkcontainercellaccessible.c \ gtkentryaccessible.c \ gtkexpanderaccessible.c \ + gtkflowboxaccessible.c \ + gtkflowboxchildaccessible.c \ gtkframeaccessible.c \ gtkiconviewaccessible.c \ gtkimageaccessible.c \ diff --git a/gtk/a11y/gtkflowboxaccessible.c b/gtk/a11y/gtkflowboxaccessible.c new file mode 100644 index 0000000000..d32a7f2db0 --- /dev/null +++ b/gtk/a11y/gtkflowboxaccessible.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gtkflowboxaccessibleprivate.h" + +#include "gtk/gtkflowbox.h" + +static void atk_selection_interface_init (AtkSelectionIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GtkFlowBoxAccessible, gtk_flow_box_accessible, GTK_TYPE_CONTAINER_ACCESSIBLE, + G_IMPLEMENT_INTERFACE (ATK_TYPE_SELECTION, atk_selection_interface_init)) + +static void +gtk_flow_box_accessible_init (GtkFlowBoxAccessible *accessible) +{ +} + +static void +gtk_flow_box_accessible_initialize (AtkObject *obj, + gpointer data) +{ + ATK_OBJECT_CLASS (gtk_flow_box_accessible_parent_class)->initialize (obj, data); + + obj->role = ATK_ROLE_TABLE; +} + +static AtkStateSet* +gtk_flow_box_accessible_ref_state_set (AtkObject *obj) +{ + AtkStateSet *state_set; + GtkWidget *widget; + + state_set = ATK_OBJECT_CLASS (gtk_flow_box_accessible_parent_class)->ref_state_set (obj); + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj)); + + if (widget != NULL) + atk_state_set_add_state (state_set, ATK_STATE_MANAGES_DESCENDANTS); + + return state_set; +} + +static void +gtk_flow_box_accessible_class_init (GtkFlowBoxAccessibleClass *klass) +{ + AtkObjectClass *object_class = ATK_OBJECT_CLASS (klass); + + object_class->initialize = gtk_flow_box_accessible_initialize; + object_class->ref_state_set = gtk_flow_box_accessible_ref_state_set; +} + +static gboolean +gtk_flow_box_accessible_add_selection (AtkSelection *selection, + gint idx) +{ + GtkWidget *box; + GList *children; + GtkWidget *child; + + box = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection)); + if (box == NULL) + return FALSE; + + children = gtk_container_get_children (GTK_CONTAINER (box)); + child = g_list_nth_data (children, idx); + g_list_free (children); + if (child) + { + gtk_flow_box_select_child (GTK_FLOW_BOX (box), GTK_FLOW_BOX_CHILD (child)); + return TRUE; + } + return FALSE; +} + +static gboolean +gtk_flow_box_accessible_remove_selection (AtkSelection *selection, + gint idx) +{ + GtkWidget *box; + GList *children; + GtkWidget *child; + + box = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection)); + if (box == NULL) + return FALSE; + + children = gtk_container_get_children (GTK_CONTAINER (box)); + child = g_list_nth_data (children, idx); + g_list_free (children); + if (child) + { + gtk_flow_box_unselect_child (GTK_FLOW_BOX (box), GTK_FLOW_BOX_CHILD (child)); + return TRUE; + } + return FALSE; +} + +static gboolean +gtk_flow_box_accessible_clear_selection (AtkSelection *selection) +{ + GtkWidget *box; + + box = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection)); + if (box == NULL) + return FALSE; + + gtk_flow_box_unselect_all (GTK_FLOW_BOX (box)); + return TRUE; +} + +static gboolean +gtk_flow_box_accessible_select_all (AtkSelection *selection) +{ + GtkWidget *box; + + box = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection)); + if (box == NULL) + return FALSE; + + gtk_flow_box_select_all (GTK_FLOW_BOX (box)); + return TRUE; +} + +typedef struct +{ + gint idx; + GtkWidget *child; +} FindSelectedData; + +static void +find_selected_child (GtkFlowBox *box, + GtkFlowBoxChild *child, + gpointer data) +{ + FindSelectedData *d = data; + + if (d->idx == 0) + { + if (d->child == NULL) + d->child = GTK_WIDGET (child); + } + else + d->idx -= 1; +} + +static AtkObject * +gtk_flow_box_accessible_ref_selection (AtkSelection *selection, + gint idx) +{ + GtkWidget *box; + AtkObject *accessible; + FindSelectedData data; + + box = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection)); + if (box == NULL) + return NULL; + + data.idx = idx; + data.child = NULL; + gtk_flow_box_selected_foreach (GTK_FLOW_BOX (box), find_selected_child, &data); + + if (data.child == NULL) + return NULL; + + accessible = gtk_widget_get_accessible (data.child); + g_object_ref (accessible); + return accessible; +} + +static void +count_selected (GtkFlowBox *box, + GtkFlowBoxChild *child, + gpointer data) +{ + gint *count = data; + *count += 1; +} + +static gint +gtk_flow_box_accessible_get_selection_count (AtkSelection *selection) +{ + GtkWidget *box; + gint count; + + box = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection)); + if (box == NULL) + return 0; + + count = 0; + gtk_flow_box_selected_foreach (GTK_FLOW_BOX (box), count_selected, &count); + + return count; +} + +static gboolean +gtk_flow_box_accessible_is_child_selected (AtkSelection *selection, + gint idx) +{ + GtkWidget *box; + GtkFlowBoxChild *child; + + box = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection)); + if (box == NULL) + return FALSE; + + child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (box), idx); + + return gtk_flow_box_child_is_selected (child); +} + +static void atk_selection_interface_init (AtkSelectionIface *iface) +{ + iface->add_selection = gtk_flow_box_accessible_add_selection; + iface->remove_selection = gtk_flow_box_accessible_remove_selection; + iface->clear_selection = gtk_flow_box_accessible_clear_selection; + iface->ref_selection = gtk_flow_box_accessible_ref_selection; + iface->get_selection_count = gtk_flow_box_accessible_get_selection_count; + iface->is_child_selected = gtk_flow_box_accessible_is_child_selected; + iface->select_all_selection = gtk_flow_box_accessible_select_all; +} + +void +_gtk_flow_box_accessible_selection_changed (GtkWidget *box) +{ + AtkObject *accessible; + accessible = gtk_widget_get_accessible (box); + g_signal_emit_by_name (accessible, "selection-changed"); +} + +void +_gtk_flow_box_accessible_update_cursor (GtkWidget *box, + GtkWidget *child) +{ + AtkObject *accessible; + AtkObject *descendant; + accessible = gtk_widget_get_accessible (box); + descendant = child ? gtk_widget_get_accessible (child) : NULL; + g_signal_emit_by_name (accessible, "active-descendant-changed", descendant); +} diff --git a/gtk/a11y/gtkflowboxaccessible.h b/gtk/a11y/gtkflowboxaccessible.h new file mode 100644 index 0000000000..51945721e3 --- /dev/null +++ b/gtk/a11y/gtkflowboxaccessible.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GTK_FLOW_BOX_ACCESSIBLE_H__ +#define __GTK_FLOW_BOX_ACCESSIBLE_H__ + +#if !defined (__GTK_A11Y_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_FLOW_BOX_ACCESSIBLE (gtk_flow_box_accessible_get_type ()) +#define GTK_FLOW_BOX_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FLOW_BOX_ACCESSIBLE, GtkFlowBoxAccessible)) +#define GTK_FLOW_BOX_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FLOW_BOX_ACCESSIBLE, GtkFlowBoxAccessibleClass)) +#define GTK_IS_FLOW_BOX_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FLOW_BOX_ACCESSIBLE)) +#define GTK_IS_FLOW_BOX_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FLOW_BOX_ACCESSIBLE)) +#define GTK_FLOW_BOX_ACCESSIBLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FLOW_BOX_ACCESSIBLE, GtkFlowBoxAccessibleClass)) + +typedef struct _GtkFlowBoxAccessible GtkFlowBoxAccessible; +typedef struct _GtkFlowBoxAccessibleClass GtkFlowBoxAccessibleClass; +typedef struct _GtkFlowBoxAccessiblePrivate GtkFlowBoxAccessiblePrivate; + +struct _GtkFlowBoxAccessible +{ + GtkContainerAccessible parent; + + GtkFlowBoxAccessiblePrivate *priv; +}; + +struct _GtkFlowBoxAccessibleClass +{ + GtkContainerAccessibleClass parent_class; +}; + +GDK_AVAILABLE_IN_3_12 +GType gtk_flow_box_accessible_get_type (void); + +G_END_DECLS + +#endif /* __GTK_FLOW_BOX_ACCESSIBLE_H__ */ diff --git a/gtk/a11y/gtkflowboxaccessibleprivate.h b/gtk/a11y/gtkflowboxaccessibleprivate.h new file mode 100644 index 0000000000..df19ba782a --- /dev/null +++ b/gtk/a11y/gtkflowboxaccessibleprivate.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GTK_FLOW_BOX_ACCESSIBLE_PRIVATE_H__ +#define __GTK_FLOW_BOX_ACCESSIBLE_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +void _gtk_flow_box_accessible_selection_changed (GtkWidget *box); +void _gtk_flow_box_accessible_update_cursor (GtkWidget *box, + GtkWidget *child); +G_END_DECLS + +#endif /* __GTK_FLOW_BOX_ACCESSIBLE_PRIVATE_H__ */ diff --git a/gtk/a11y/gtkflowboxchildaccessible.c b/gtk/a11y/gtkflowboxchildaccessible.c new file mode 100644 index 0000000000..2476f97ab0 --- /dev/null +++ b/gtk/a11y/gtkflowboxchildaccessible.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gtkflowboxchildaccessible.h" + +#include "gtk/gtkflowbox.h" + + +G_DEFINE_TYPE (GtkFlowBoxChildAccessible, gtk_flow_box_child_accessible, GTK_TYPE_CONTAINER_ACCESSIBLE) + +static void +gtk_flow_box_child_accessible_init (GtkFlowBoxChildAccessible *accessible) +{ +} + +static void +gtk_flow_box_child_accessible_initialize (AtkObject *obj, + gpointer data) +{ + ATK_OBJECT_CLASS (gtk_flow_box_child_accessible_parent_class)->initialize (obj, data); + + obj->role = ATK_ROLE_TABLE_CELL; +} + +static AtkStateSet * +gtk_flow_box_child_accessible_ref_state_set (AtkObject *obj) +{ + AtkStateSet *state_set; + GtkWidget *widget, *parent; + + state_set = ATK_OBJECT_CLASS (gtk_flow_box_child_accessible_parent_class)->ref_state_set (obj); + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj)); + if (widget != NULL) + { + parent = gtk_widget_get_parent (widget); + if (gtk_flow_box_get_selection_mode (GTK_FLOW_BOX (parent)) != GTK_SELECTION_NONE) + atk_state_set_add_state (state_set, ATK_STATE_SELECTABLE); + + if (gtk_flow_box_child_is_selected (GTK_FLOW_BOX_CHILD (widget))) + atk_state_set_add_state (state_set, ATK_STATE_SELECTED); + } + + return state_set; +} + +static void +gtk_flow_box_child_accessible_class_init (GtkFlowBoxChildAccessibleClass *klass) +{ + AtkObjectClass *object_class = ATK_OBJECT_CLASS (klass); + + object_class->initialize = gtk_flow_box_child_accessible_initialize; + object_class->ref_state_set = gtk_flow_box_child_accessible_ref_state_set; +} diff --git a/gtk/a11y/gtkflowboxchildaccessible.h b/gtk/a11y/gtkflowboxchildaccessible.h new file mode 100644 index 0000000000..0a52d3366d --- /dev/null +++ b/gtk/a11y/gtkflowboxchildaccessible.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GTK_FLOW_BOX_CHILD_ACCESSIBLE_H__ +#define __GTK_FLOW_BOX_CHILD_ACCESSIBLE_H__ + +#if !defined (__GTK_A11Y_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_FLOW_BOX_CHILD_ACCESSIBLE (gtk_flow_box_child_accessible_get_type ()) +#define GTK_FLOW_BOX_CHILD_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FLOW_BOX_CHILD_ACCESSIBLE, GtkFlowBoxChildAccessible)) +#define GTK_FLOW_BOX_CHILD_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FLOW_BOX_CHILD_ACCESSIBLE, GtkFlowBoxChildAccessibleClass)) +#define GTK_IS_FLOW_BOX_CHILD_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FLOW_BOX_CHILD_ACCESSIBLE)) +#define GTK_IS_FLOW_BOX_CHILD_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FLOW_BOX_CHILD_ACCESSIBLE)) +#define GTK_FLOW_BOX_CHILD_ACCESSIBLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FLOW_BOX_CHILD_ACCESSIBLE, GtkFlowBoxChildAccessibleClass)) + +typedef struct _GtkFlowBoxChildAccessible GtkFlowBoxChildAccessible; +typedef struct _GtkFlowBoxChildAccessibleClass GtkFlowBoxChildAccessibleClass; + +struct _GtkFlowBoxChildAccessible +{ + GtkContainerAccessible parent; +}; + +struct _GtkFlowBoxChildAccessibleClass +{ + GtkContainerAccessibleClass parent_class; +}; + +GDK_AVAILABLE_IN_3_12 +GType gtk_flow_box_child_accessible_get_type (void); + +G_END_DECLS + +#endif /* __GTK_FLOW_BOX_CHILD_ACCESSIBLE_H__ */ diff --git a/gtk/gtk-a11y.h b/gtk/gtk-a11y.h index ba46bcdcdb..8e395a6486 100644 --- a/gtk/gtk-a11y.h +++ b/gtk/gtk-a11y.h @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/gtk/gtk.h b/gtk/gtk.h index fa20539147..a3413db3c6 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -99,6 +99,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkflowbox.c b/gtk/gtkflowbox.c new file mode 100644 index 0000000000..436ac9f137 --- /dev/null +++ b/gtk/gtkflowbox.c @@ -0,0 +1,4719 @@ +/* + * Copyright (C) 2007-2010 Openismus GmbH + * Copyright (C) 2013 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Tristan Van Berkom + * Matthias Clasen + * William Jon McCann + */ + +/* Preamble {{{1 */ + +/** + * SECTION:gtkflowbox + * @Short_Description: A container that allows reflowing its children + * @Title: GtkFlowBox + * + * An GtkFlowBox positions child widgets in sequence according to its + * orientation. + * + * For instance, with the horizontal orientation, the widgets will be + * arranged from left to right, starting a new row under the previous + * row when necessary. Reducing the width in this case will require more + * rows, so a larger height will be requested. + * + * Likewise, with the vertical orientation, the widgets will be arranged + * from top to bottom, starting a new column to the right when necessary. + * Reducing the height will require more columns, so a larger width will + * be requested. + * + * The children of a GtkFlowBox can be dynamically sorted and filtered. + * + * Although a GtkFlowBox must have only #GtkFlowBoxChild children, + * you can add any kind of widget to it via gtk_container_add(), and + * a GtkFlowBoxChild widget will automatically be inserted between + * the box and the widget. + * + * Also see #GtkListBox. + * + * GtkFlowBox was added in GTK+ 3.12. + */ + +#include + +#include "gtkflowbox.h" +#include "gtkmarshalers.h" +#include "gtkprivate.h" +#include "gtkintl.h" + +#include "a11y/gtkflowboxaccessibleprivate.h" +#include "a11y/gtkflowboxchildaccessible.h" + +/* Forward declarations and utilities {{{1 */ + +static void gtk_flow_box_update_cursor (GtkFlowBox *box, + GtkFlowBoxChild *child); +static void gtk_flow_box_select_and_activate (GtkFlowBox *box, + GtkFlowBoxChild *child); +static void gtk_flow_box_update_selection (GtkFlowBox *box, + GtkFlowBoxChild *child, + gboolean modify, + gboolean extend); +static void gtk_flow_box_apply_filter (GtkFlowBox *box, + GtkFlowBoxChild *child); +static void gtk_flow_box_apply_sort (GtkFlowBox *box, + GtkFlowBoxChild *child); +static gint gtk_flow_box_sort (GtkFlowBoxChild *a, + GtkFlowBoxChild *b, + GtkFlowBox *box); + +static void +get_current_selection_modifiers (GtkWidget *widget, + gboolean *modify, + gboolean *extend) +{ + GdkModifierType state = 0; + GdkModifierType mask; + + *modify = FALSE; + *extend = FALSE; + + if (gtk_get_current_event_state (&state)) + { + mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_MODIFY_SELECTION); + if ((state & mask) == mask) + *modify = TRUE; + mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_EXTEND_SELECTION); + if ((state & mask) == mask) + *extend = TRUE; + } +} + +static void +path_from_horizontal_line_rects (cairo_t *cr, + GdkRectangle *lines, + gint n_lines) +{ + gint start_line, end_line; + GdkRectangle *r; + gint i; + + /* Join rows vertically by extending to the middle */ + for (i = 0; i < n_lines - 1; i++) + { + GdkRectangle *r1 = &lines[i]; + GdkRectangle *r2 = &lines[i+1]; + gint gap, old; + + gap = r2->y - (r1->y + r1->height); + r1->height += gap / 2; + old = r2->y; + r2->y = r1->y + r1->height; + r2->height += old - r2->y; + } + + cairo_new_path (cr); + start_line = 0; + + do + { + for (i = start_line; i < n_lines; i++) + { + r = &lines[i]; + if (i == start_line) + cairo_move_to (cr, r->x + r->width, r->y); + else + cairo_line_to (cr, r->x + r->width, r->y); + cairo_line_to (cr, r->x + r->width, r->y + r->height); + + if (i < n_lines - 1 && + (r->x + r->width < lines[i+1].x || + r->x > lines[i+1].x + lines[i+1].width)) + { + i++; + break; + } + } + end_line = i; + for (i = end_line - 1; i >= start_line; i--) + { + r = &lines[i]; + cairo_line_to (cr, r->x, r->y + r->height); + cairo_line_to (cr, r->x, r->y); + } + cairo_close_path (cr); + start_line = end_line; + } + while (end_line < n_lines); +} + +static void +path_from_vertical_line_rects (cairo_t *cr, + GdkRectangle *lines, + gint n_lines) +{ + gint start_line, end_line; + GdkRectangle *r; + gint i; + + /* Join rows horizontally by extending to the middle */ + for (i = 0; i < n_lines - 1; i++) + { + GdkRectangle *r1 = &lines[i]; + GdkRectangle *r2 = &lines[i+1]; + gint gap, old; + + gap = r2->x - (r1->x + r1->width); + r1->width += gap / 2; + old = r2->x; + r2->x = r1->x + r1->width; + r2->width += old - r2->x; + } + + cairo_new_path (cr); + start_line = 0; + + do + { + for (i = start_line; i < n_lines; i++) + { + r = &lines[i]; + if (i == start_line) + cairo_move_to (cr, r->x, r->y + r->height); + else + cairo_line_to (cr, r->x, r->y + r->height); + cairo_line_to (cr, r->x + r->width, r->y + r->height); + + if (i < n_lines - 1 && + (r->y + r->height < lines[i+1].y || + r->y > lines[i+1].y + lines[i+1].height)) + { + i++; + break; + } + } + end_line = i; + for (i = end_line - 1; i >= start_line; i--) + { + r = &lines[i]; + cairo_line_to (cr, r->x + r->width, r->y); + cairo_line_to (cr, r->x, r->y); + } + cairo_close_path (cr); + start_line = end_line; + } + while (end_line < n_lines); +} + +/* GtkFlowBoxChild {{{1 */ + +/* GObject boilerplate {{{2 */ + +enum { + CHILD_ACTIVATE, + CHILD_LAST_SIGNAL +}; + +static guint child_signals[CHILD_LAST_SIGNAL] = { 0 }; + +typedef struct _GtkFlowBoxChildPrivate GtkFlowBoxChildPrivate; +struct _GtkFlowBoxChildPrivate +{ + GSequenceIter *iter; + gboolean selected; +}; + +#define CHILD_PRIV(child) ((GtkFlowBoxChildPrivate*)gtk_flow_box_child_get_instance_private ((GtkFlowBoxChild*)(child))) + +G_DEFINE_TYPE_WITH_PRIVATE (GtkFlowBoxChild, gtk_flow_box_child, GTK_TYPE_BIN) + +/* Internal API {{{2 */ + +static GtkFlowBox * +gtk_flow_box_child_get_box (GtkFlowBoxChild *child) +{ + GtkWidget *parent; + + parent = gtk_widget_get_parent (GTK_WIDGET (child)); + if (parent && GTK_IS_FLOW_BOX (parent)) + return GTK_FLOW_BOX (parent); + + return NULL; +} + +static void +gtk_flow_box_child_set_focus (GtkFlowBoxChild *child) +{ + GtkFlowBox *box = gtk_flow_box_child_get_box (child); + gboolean modify_selection; + gboolean extend_selection; + + get_current_selection_modifiers (GTK_WIDGET (box), &modify_selection, &extend_selection); + + if (modify_selection) + gtk_flow_box_update_cursor (box, child); + else + gtk_flow_box_update_selection (box, child, FALSE, FALSE); +} + +/* GtkWidget implementation {{{2 */ + +static gboolean +gtk_flow_box_child_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + gboolean had_focus = FALSE; + GtkWidget *child; + + child = gtk_bin_get_child (GTK_BIN (widget)); + + g_object_get (widget, "has-focus", &had_focus, NULL); + if (had_focus) + { + /* If on row, going right, enter into possible container */ + if (child && + (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD)) + { + if (gtk_widget_child_focus (GTK_WIDGET (child), direction)) + return TRUE; + } + + return FALSE; + } + else if (gtk_container_get_focus_child (GTK_CONTAINER (widget)) != NULL) + { + /* Child has focus, always navigate inside it first */ + if (gtk_widget_child_focus (child, direction)) + return TRUE; + + /* If exiting child container to the left, select child */ + if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD) + { + gtk_flow_box_child_set_focus (GTK_FLOW_BOX_CHILD (widget)); + return TRUE; + } + + return FALSE; + } + else + { + /* If coming from the left, enter into possible container */ + if (child && + (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)) + { + if (gtk_widget_child_focus (child, direction)) + return TRUE; + } + + gtk_flow_box_child_set_focus (GTK_FLOW_BOX_CHILD (widget)); + return TRUE; + } +} + +static void +gtk_flow_box_child_activate (GtkFlowBoxChild *child) +{ + GtkFlowBox *box; + + box = gtk_flow_box_child_get_box (child); + if (box) + gtk_flow_box_select_and_activate (box, child); +} + +static gboolean +gtk_flow_box_child_draw (GtkWidget *widget, + cairo_t *cr) +{ + GtkAllocation allocation = {0}; + GtkStyleContext* context; + GtkStateFlags state; + GtkBorder border; + gint focus_pad; + + gtk_widget_get_allocation (widget, &allocation); + context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); + + gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height); + gtk_render_frame (context, cr, 0, 0, allocation.width, allocation.height); + + if (gtk_widget_has_visible_focus (widget)) + { + gtk_style_context_get_border (context, state, &border); + + gtk_style_context_get_style (context, + "focus-padding", &focus_pad, + NULL); + gtk_render_focus (context, cr, border.left + focus_pad, border.top + focus_pad, + allocation.width - 2 * focus_pad - border.left - border.right, + allocation.height - 2 * focus_pad - border.top - border.bottom); + } + + GTK_WIDGET_CLASS (gtk_flow_box_child_parent_class)->draw (widget, cr); + + return TRUE; +} + +/* Size allocation {{{3 */ + +static void +gtk_flow_box_child_get_full_border (GtkFlowBoxChild *child, + GtkBorder *full_border) +{ + GtkWidget *widget = GTK_WIDGET (child); + GtkStyleContext *context; + GtkStateFlags state; + GtkBorder padding, border; + int focus_width, focus_pad; + + context = gtk_widget_get_style_context (widget); + state = gtk_style_context_get_state (context); + + gtk_style_context_get_padding (context, state, &padding); + gtk_style_context_get_border (context, state, &border); + gtk_style_context_get_style (context, + "focus-line-width", &focus_width, + "focus-padding", &focus_pad, + NULL); + + full_border->left = padding.left + border.left + focus_width + focus_pad; + full_border->right = padding.right + border.right + focus_width + focus_pad; + full_border->top = padding.top + border.top + focus_width + focus_pad; + full_border->bottom = padding.bottom + border.bottom + focus_width + focus_pad; +} + +static GtkSizeRequestMode +gtk_flow_box_child_get_request_mode (GtkWidget *widget) +{ + GtkFlowBox *box; + + box = gtk_flow_box_child_get_box (GTK_FLOW_BOX_CHILD (widget)); + if (box) + return gtk_widget_get_request_mode (GTK_WIDGET (box)); + else + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void gtk_flow_box_child_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width); +static void gtk_flow_box_child_get_preferred_height (GtkWidget *widget, + gint *minimum_height, + gint *natural_height); + +static void +gtk_flow_box_child_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) + { + GtkWidget *child; + gint child_min = 0, child_natural = 0; + GtkBorder full_border = { 0, }; + + gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border); + child = gtk_bin_get_child (GTK_BIN (widget)); + if (child && gtk_widget_get_visible (child)) + gtk_widget_get_preferred_height_for_width (child, width - full_border.left - full_border.right, + &child_min, &child_natural); + + if (minimum_height) + *minimum_height = full_border.top + child_min + full_border.bottom; + if (natural_height) + *natural_height = full_border.top + child_natural + full_border.bottom; + } + else + { + gtk_flow_box_child_get_preferred_height (widget, minimum_height, natural_height); + } +} + +static void +gtk_flow_box_child_get_preferred_width (GtkWidget *widget, + gint *minimum_width, + gint *natural_width) +{ + if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) + { + GtkWidget *child; + gint child_min = 0, child_natural = 0; + GtkBorder full_border = { 0, }; + + gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border); + child = gtk_bin_get_child (GTK_BIN (widget)); + if (child && gtk_widget_get_visible (child)) + gtk_widget_get_preferred_width (child, &child_min, &child_natural); + + if (minimum_width) + *minimum_width = full_border.left + child_min + full_border.right; + if (natural_width) + *natural_width = full_border.left + child_natural + full_border.right; + } + else + { + gint natural_height; + + gtk_flow_box_child_get_preferred_height (widget, NULL, &natural_height); + gtk_flow_box_child_get_preferred_width_for_height (widget, natural_height, + minimum_width, natural_width); + } +} + +static void +gtk_flow_box_child_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) + { + gtk_flow_box_child_get_preferred_width (widget, minimum_width, natural_width); + } + else + { + GtkWidget *child; + gint child_min = 0, child_natural = 0; + GtkBorder full_border = { 0, }; + + gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border); + child = gtk_bin_get_child (GTK_BIN (widget)); + if (child && gtk_widget_get_visible (child)) + gtk_widget_get_preferred_width_for_height (child, height - full_border.top - full_border.bottom, + &child_min, &child_natural); + + if (minimum_width) + *minimum_width = full_border.left + child_min + full_border.right; + if (natural_width) + *natural_width = full_border.left + child_natural + full_border.right; + } +} + +static void +gtk_flow_box_child_get_preferred_height (GtkWidget *widget, + gint *minimum_height, + gint *natural_height) +{ + if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) + { + gint natural_width; + + gtk_flow_box_child_get_preferred_width (widget, NULL, &natural_width); + gtk_flow_box_child_get_preferred_height_for_width (widget, natural_width, + minimum_height, natural_height); + } + else + { + GtkWidget *child; + gint child_min = 0, child_natural = 0; + GtkBorder full_border = { 0, }; + + gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border); + child = gtk_bin_get_child (GTK_BIN (widget)); + if (child && gtk_widget_get_visible (child)) + gtk_widget_get_preferred_height (child, &child_min, &child_natural); + + if (minimum_height) + *minimum_height = full_border.top + child_min + full_border.bottom; + if (natural_height) + *natural_height = full_border.top + child_natural + full_border.bottom; + } +} + +static void +gtk_flow_box_child_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkWidget *child; + + gtk_widget_set_allocation (widget, allocation); + + child = gtk_bin_get_child (GTK_BIN (widget)); + if (child && gtk_widget_get_visible (child)) + { + GtkAllocation child_allocation; + GtkBorder border = { 0, }; + + gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &border); + + child_allocation.x = allocation->x + border.left; + child_allocation.y = allocation->y + border.top; + child_allocation.width = allocation->width - border.left - border.right; + child_allocation.height = allocation->height - border.top - border.bottom; + + child_allocation.width = MAX (1, child_allocation.width); + child_allocation.height = MAX (1, child_allocation.height); + + gtk_widget_size_allocate (child, &child_allocation); + } +} + +/* GObject implementation {{{2 */ + +static void +gtk_flow_box_child_class_init (GtkFlowBoxChildClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + widget_class->draw = gtk_flow_box_child_draw; + widget_class->get_request_mode = gtk_flow_box_child_get_request_mode; + widget_class->get_preferred_height = gtk_flow_box_child_get_preferred_height; + widget_class->get_preferred_height_for_width = gtk_flow_box_child_get_preferred_height_for_width; + widget_class->get_preferred_width = gtk_flow_box_child_get_preferred_width; + widget_class->get_preferred_width_for_height = gtk_flow_box_child_get_preferred_width_for_height; + widget_class->size_allocate = gtk_flow_box_child_size_allocate; + widget_class->focus = gtk_flow_box_child_focus; + + class->activate = gtk_flow_box_child_activate; + + /** + * GtkFlowBoxChild::activate: + * @child: The child on which the signal is emitted + * + * The ::activate signal is emitted when the user activates + * a child widget in a #GtkFlowBox, either by clicking or + * double-clicking, or by using the Space or Enter key. + * + * While this signal is used as a + * keybinding signal, + * it can be used by applications for their own purposes. + */ + child_signals[CHILD_ACTIVATE] = + g_signal_new (I_("activate"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkFlowBoxChildClass, activate), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + widget_class->activate_signal = child_signals[CHILD_ACTIVATE]; + + gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_LIST_ITEM); +} + +static void +gtk_flow_box_child_init (GtkFlowBoxChild *child) +{ + GtkStyleContext *context; + + gtk_widget_set_can_focus (GTK_WIDGET (child), TRUE); + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (child), TRUE); + + context = gtk_widget_get_style_context (GTK_WIDGET (child)); + gtk_style_context_add_class (context, "grid-child"); +} + +/* Public API {{{2 */ + +/** + * gtk_flow_box_child_new: + * + * Creates a new #GtkFlowBoxChild, to be used as a child + * of a #GtkFlowBox. + * + * Returns: a new #GtkFlowBoxChild + * + * Since: 3.12 + */ +GtkWidget * +gtk_flow_box_child_new (void) +{ + return g_object_new (GTK_TYPE_FLOW_BOX_CHILD, NULL); +} + +/** + * gtk_flow_box_child_get_index: + * @child: a #GtkFlowBoxChild + * + * Gets the current index of the @child in its #GtkFlowBox container. + * + * Returns: the index of the @child, or -1 if the @child is not + * in a flow box. + * + * Since: 3.12 + */ +gint +gtk_flow_box_child_get_index (GtkFlowBoxChild *child) +{ + GtkFlowBoxChildPrivate *priv; + + g_return_val_if_fail (GTK_IS_FLOW_BOX_CHILD (child), -1); + + priv = CHILD_PRIV (child); + + if (priv->iter != NULL) + return g_sequence_iter_get_position (priv->iter); + + return -1; +} + +/** + * gtk_flow_box_child_is_selected: + * @child: a #GtkFlowBoxChild + * + * Returns whether the @child is currently selected in its + * #GtkFlowBox container. + * + * Returns: %TRUE if @child is selected + * + * Since: 3.12 + */ +gboolean +gtk_flow_box_child_is_selected (GtkFlowBoxChild *child) +{ + g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child)); + + return CHILD_PRIV (child)->selected; +} + +/** + * gtk_flow_box_child_changed: + * @child: a #GtkFlowBoxChild + * + * Marks @child as changed, causing any state that depends on this + * to be updated. This affects sorting and filtering. + * + * Note that calls to this method must be in sync with the data + * used for the sorting and filtering functions. For instance, if + * the list is mirroring some external data set, and *two* children + * changed in the external data set when you call + * gtk_flow_box_child_changed() on the first child, the sort function + * must only read the new data for the first of the two changed + * children, otherwise the resorting of the children will be wrong. + * + * This generally means that if you don't fully control the data + * model, you have to duplicate the data that affects the sorting + * and filtering functions into the widgets themselves. Another + * alternative is to call gtk_flow_box_invalidate_sort() on any + * model change, but that is more expensive. + * + * Since: 3.12 + */ +void +gtk_flow_box_child_changed (GtkFlowBoxChild *child) +{ + GtkFlowBox *box; + + g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child)); + + box = gtk_flow_box_child_get_box (child); + + if (box == NULL) + return; + + gtk_flow_box_apply_sort (box, child); + gtk_flow_box_apply_filter (box, child); +} + +/* GtkFlowBox {{{1 */ + +/* Constants {{{2 */ + +#define DEFAULT_MAX_CHILDREN_PER_LINE 7 +#define RUBBERBAND_START_DISTANCE 32 +#define AUTOSCROLL_FAST_DISTANCE 32 +#define AUTOSCROLL_FACTOR 20 +#define AUTOSCROLL_FACTOR_FAST 10 + +/* GObject boilerplate {{{2 */ + +enum { + CHILD_ACTIVATED, + SELECTED_CHILDREN_CHANGED, + ACTIVATE_CURSOR_CHILD, + TOGGLE_CURSOR_CHILD, + MOVE_CURSOR, + SELECT_ALL, + UNSELECT_ALL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +enum { + PROP_0, + PROP_ORIENTATION, + PROP_HOMOGENEOUS, + PROP_HALIGN_POLICY, + PROP_VALIGN_POLICY, + PROP_COLUMN_SPACING, + PROP_ROW_SPACING, + PROP_MIN_CHILDREN_PER_LINE, + PROP_MAX_CHILDREN_PER_LINE, + PROP_SELECTION_MODE, + PROP_ACTIVATE_ON_SINGLE_CLICK +}; + +typedef struct _GtkFlowBoxPrivate GtkFlowBoxPrivate; +struct _GtkFlowBoxPrivate { + GtkOrientation orientation; + gboolean homogeneous; + + guint row_spacing; + guint column_spacing; + + GtkFlowBoxChild *prelight_child; + GtkFlowBoxChild *cursor_child; + GtkFlowBoxChild *selected_child; + + gboolean active_child_active; + GtkFlowBoxChild *active_child; + + GtkSelectionMode selection_mode; + + GtkAdjustment *hadjustment; + GtkAdjustment *vadjustment; + gboolean activate_on_single_click; + + guint16 min_children_per_line; + guint16 max_children_per_line; + guint16 cur_children_per_line; + + GSequence *children; + + GtkFlowBoxFilterFunc filter_func; + gpointer filter_data; + GDestroyNotify filter_destroy; + + GtkFlowBoxSortFunc sort_func; + gpointer sort_data; + GDestroyNotify sort_destroy; + + gboolean track_motion; + gboolean rubberband_select; + GtkFlowBoxChild *rubberband_first; + GtkFlowBoxChild *rubberband_last; + gint button_down_x; + gint button_down_y; + GdkDevice *rubberband_device; + + GtkScrollType autoscroll_mode; + guint autoscroll_id; +}; + +#define BOX_PRIV(box) ((GtkFlowBoxPrivate*)gtk_flow_box_get_instance_private ((GtkFlowBox*)(box))) + +G_DEFINE_TYPE_WITH_CODE (GtkFlowBox, gtk_flow_box, GTK_TYPE_CONTAINER, + G_ADD_PRIVATE (GtkFlowBox) + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)) + +/* Internal API, utilities {{{2 */ + +#define ORIENTATION_ALIGN(box) \ + (BOX_PRIV(box)->orientation == GTK_ORIENTATION_HORIZONTAL \ + ? gtk_widget_get_halign (GTK_WIDGET (box)) \ + : gtk_widget_get_valign (GTK_WIDGET (box))) + +#define OPPOSING_ORIENTATION_ALIGN(box) \ + (BOX_PRIV(box)->orientation == GTK_ORIENTATION_HORIZONTAL \ + ? gtk_widget_get_valign (GTK_WIDGET (box)) \ + : gtk_widget_get_halign (GTK_WIDGET (box))) + +/* Children are visible if they are shown by the app (visible) + * and not filtered out (child_visible) by the box + */ +static inline gboolean +child_is_visible (GtkWidget *child) +{ + return gtk_widget_get_visible (child) && + gtk_widget_get_child_visible (child); +} + +static gint +get_visible_children (GtkFlowBox *box) +{ + GSequenceIter *iter; + gint i = 0; + + for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkWidget *child; + + child = g_sequence_get (iter); + if (child_is_visible (child)) + i++; + } + + return i; +} + +static GtkFlowBoxChild * +gtk_flow_box_find_child_at_pos (GtkFlowBox *box, + gint x, + gint y) +{ + GtkWidget *child; + GSequenceIter *iter; + GtkAllocation allocation; + + for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + child = g_sequence_get (iter); + if (!child_is_visible (child)) + continue; + gtk_widget_get_allocation (child, &allocation); + if (x >= allocation.x && x < (allocation.x + allocation.width) && + y >= allocation.y && y < (allocation.y + allocation.height)) + return GTK_FLOW_BOX_CHILD (child); + } + + return NULL; +} + +static void +gtk_flow_box_update_prelight (GtkFlowBox *box, + GtkFlowBoxChild *child) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + + if (child != priv->prelight_child) + { + priv->prelight_child = child; + gtk_widget_queue_draw (GTK_WIDGET (box)); + } +} + +static void +gtk_flow_box_update_active (GtkFlowBox *box, + GtkFlowBoxChild *child) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + gboolean val; + + val = priv->active_child == child; + if (priv->active_child != NULL && + val != priv->active_child_active) + { + priv->active_child_active = val; + gtk_widget_queue_draw (GTK_WIDGET (box)); + } +} + +static void +gtk_flow_box_apply_filter (GtkFlowBox *box, + GtkFlowBoxChild *child) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + gboolean do_show; + + do_show = TRUE; + if (priv->filter_func != NULL) + do_show = priv->filter_func (child, priv->filter_data); + + gtk_widget_set_child_visible (GTK_WIDGET (child), do_show); +} + +static void +gtk_flow_box_apply_sort (GtkFlowBox *box, + GtkFlowBoxChild *child) +{ + if (BOX_PRIV (box)->sort_func != NULL) + { + g_sequence_sort_changed (CHILD_PRIV (child)->iter, + (GCompareDataFunc)gtk_flow_box_sort, box); + gtk_widget_queue_resize (GTK_WIDGET (box)); + } +} + +/* Selection utilities {{{3 */ + +static gboolean +gtk_flow_box_child_set_selected (GtkFlowBoxChild *child, + gboolean selected) +{ + GtkFlowBox *box; + + if (CHILD_PRIV (child)->selected != selected) + { + CHILD_PRIV (child)->selected = selected; + if (selected) + gtk_widget_set_state_flags (GTK_WIDGET (child), + GTK_STATE_FLAG_SELECTED, FALSE); + else + gtk_widget_unset_state_flags (GTK_WIDGET (child), + GTK_STATE_FLAG_SELECTED); + + box = gtk_flow_box_child_get_box (child); + _gtk_flow_box_accessible_selection_changed (GTK_WIDGET (box)); + + gtk_widget_queue_draw (GTK_WIDGET (child)); + + return TRUE; + } + + return FALSE; +} + +static gboolean +gtk_flow_box_unselect_all_internal (GtkFlowBox *box) +{ + GtkFlowBoxChild *child; + GSequenceIter *iter; + gboolean dirty = FALSE; + + if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE) + return FALSE; + + for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + child = g_sequence_get (iter); + dirty |= gtk_flow_box_child_set_selected (child, FALSE); + } + + return dirty; +} + +static void +gtk_flow_box_unselect_child_info (GtkFlowBox *box, + GtkFlowBoxChild *child) +{ + if (!CHILD_PRIV (child)->selected) + return; + + if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE) + return; + else if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE) + gtk_flow_box_unselect_all_internal (box); + else + gtk_flow_box_child_set_selected (child, FALSE); + + g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0); +} + +static void +gtk_flow_box_update_cursor (GtkFlowBox *box, + GtkFlowBoxChild *child) +{ + BOX_PRIV (box)->cursor_child = child; + gtk_widget_grab_focus (GTK_WIDGET (child)); + gtk_widget_queue_draw (GTK_WIDGET (child)); + _gtk_flow_box_accessible_update_cursor (GTK_WIDGET (box), GTK_WIDGET (child)); +} + +static void +gtk_flow_box_select_child_info (GtkFlowBox *box, + GtkFlowBoxChild *child) +{ + if (CHILD_PRIV (child)->selected) + return; + + if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE) + return; + if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE) + gtk_flow_box_unselect_all_internal (box); + + gtk_flow_box_child_set_selected (child, TRUE); + BOX_PRIV (box)->selected_child = child; + + g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0); + + gtk_flow_box_update_cursor (box, child); +} + +static void +gtk_flow_box_select_all_between (GtkFlowBox *box, + GtkFlowBoxChild *child1, + GtkFlowBoxChild *child2) +{ + GSequenceIter *iter, *iter1, *iter2; + + if (child1) + iter1 = CHILD_PRIV (child1)->iter; + else + iter1 = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + + if (child2) + iter2 = CHILD_PRIV (child2)->iter; + else + iter2 = g_sequence_get_end_iter (BOX_PRIV (box)->children); + + if (g_sequence_iter_compare (iter2, iter1) < 0) + { + iter = iter1; + iter1 = iter2; + iter2 = iter; + } + + for (iter = iter1; + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkWidget *child; + + child = g_sequence_get (iter); + if (child_is_visible (child)) + gtk_flow_box_child_set_selected (GTK_FLOW_BOX_CHILD (child), TRUE); + + if (g_sequence_iter_compare (iter, iter2) == 0) + break; + } +} + +static void +gtk_flow_box_update_selection (GtkFlowBox *box, + GtkFlowBoxChild *child, + gboolean modify, + gboolean extend) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + + gtk_flow_box_update_cursor (box, child); + + if (priv->selection_mode == GTK_SELECTION_NONE) + return; + + if (priv->selection_mode == GTK_SELECTION_BROWSE) + { + gtk_flow_box_unselect_all_internal (box); + gtk_flow_box_child_set_selected (child, TRUE); + priv->selected_child = child; + } + else if (priv->selection_mode == GTK_SELECTION_SINGLE) + { + gboolean was_selected; + + was_selected = CHILD_PRIV (child)->selected; + gtk_flow_box_unselect_all_internal (box); + gtk_flow_box_child_set_selected (child, modify ? !was_selected : TRUE); + priv->selected_child = CHILD_PRIV (child)->selected ? child : NULL; + } + else /* GTK_SELECTION_MULTIPLE */ + { + if (extend) + { + gtk_flow_box_unselect_all_internal (box); + if (priv->selected_child == NULL) + { + gtk_flow_box_child_set_selected (child, TRUE); + priv->selected_child = child; + } + else + gtk_flow_box_select_all_between (box, priv->selected_child, child); + } + else + { + gtk_flow_box_child_set_selected (child, modify ? !CHILD_PRIV (child)->selected : TRUE); + priv->selected_child = child; + } + } + + g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0); +} + +static void +gtk_flow_box_select_and_activate (GtkFlowBox *box, + GtkFlowBoxChild *child) +{ + if (child != NULL) + { + gtk_flow_box_select_child_info (box, child); + g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child); + } +} + +/* Focus utilities {{{3 */ + +static GSequenceIter * +gtk_flow_box_get_previous_focusable (GtkFlowBox *box, + GSequenceIter *iter) +{ + GtkFlowBoxChild *child; + + while (!g_sequence_iter_is_begin (iter)) + { + iter = g_sequence_iter_prev (iter); + child = g_sequence_get (iter); + if (child_is_visible (GTK_WIDGET (child)) && + gtk_widget_is_sensitive (GTK_WIDGET (child))) + return iter; + } + + return NULL; +} + +static GSequenceIter * +gtk_flow_box_get_next_focusable (GtkFlowBox *box, + GSequenceIter *iter) +{ + GtkFlowBoxChild *child; + + while (TRUE) + { + iter = g_sequence_iter_next (iter); + if (g_sequence_iter_is_end (iter)) + return iter; + child = g_sequence_get (iter); + if (child_is_visible (GTK_WIDGET (child)) && + gtk_widget_is_sensitive (GTK_WIDGET (child))) + return iter; + } + + return NULL; +} + +static GSequenceIter * +gtk_flow_box_get_first_focusable (GtkFlowBox *box) +{ + GSequenceIter *iter; + GtkFlowBoxChild *child; + + iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + child = g_sequence_get (iter); + if (child_is_visible (GTK_WIDGET (child)) && + gtk_widget_is_sensitive (GTK_WIDGET (child))) + return iter; + + return gtk_flow_box_get_next_focusable (box, iter); +} + +static GSequenceIter * +gtk_flow_box_get_last_focusable (GtkFlowBox *box) +{ + GSequenceIter *iter; + + iter = g_sequence_get_end_iter (BOX_PRIV (box)->children); + return gtk_flow_box_get_previous_focusable (box, iter); +} + + +static GSequenceIter * +gtk_flow_box_get_above_focusable (GtkFlowBox *box, + GSequenceIter *iter) +{ + GtkFlowBoxChild *child = NULL; + gint i; + + while (TRUE) + { + i = 0; + while (i < BOX_PRIV (box)->cur_children_per_line) + { + if (g_sequence_iter_is_begin (iter)) + return NULL; + iter = g_sequence_iter_prev (iter); + child = g_sequence_get (iter); + if (child_is_visible (GTK_WIDGET (child))) + i++; + } + if (child && gtk_widget_get_sensitive (GTK_WIDGET (child))) + return iter; + } + + return NULL; +} + +static GSequenceIter * +gtk_flow_box_get_below_focusable (GtkFlowBox *box, + GSequenceIter *iter) +{ + GtkFlowBoxChild *child = NULL; + gint i; + + while (TRUE) + { + i = 0; + while (i < BOX_PRIV (box)->cur_children_per_line) + { + iter = g_sequence_iter_next (iter); + if (g_sequence_iter_is_end (iter)) + return iter; + child = g_sequence_get (iter); + if (child_is_visible (GTK_WIDGET (child))) + i++; + } + if (child && gtk_widget_get_sensitive (GTK_WIDGET (child))) + return iter; + } + + return NULL; +} + +/* GtkWidget implementation {{{2 */ + +/* Size allocation {{{3 */ + +/* Used in columned modes where all items share at least their + * equal widths or heights + */ +static void +get_max_item_size (GtkFlowBox *box, + GtkOrientation orientation, + gint *min_size, + gint *nat_size) +{ + GSequenceIter *iter; + gint max_min_size = 0; + gint max_nat_size = 0; + + for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkWidget *child; + gint child_min, child_nat; + + child = g_sequence_get (iter); + + if (!child_is_visible (child)) + continue; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_width (child, &child_min, &child_nat); + else + gtk_widget_get_preferred_height (child, &child_min, &child_nat); + + max_min_size = MAX (max_min_size, child_min); + max_nat_size = MAX (max_nat_size, child_nat); + } + + if (min_size) + *min_size = max_min_size; + + if (nat_size) + *nat_size = max_nat_size; +} + + +/* Gets the largest minimum/natural size for a given size (used to get + * the largest item heights for a fixed item width and the opposite) + */ +static void +get_largest_size_for_opposing_orientation (GtkFlowBox *box, + GtkOrientation orientation, + gint item_size, + gint *min_item_size, + gint *nat_item_size) +{ + GSequenceIter *iter; + gint max_min_size = 0; + gint max_nat_size = 0; + + for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkWidget *child; + gint child_min, child_nat; + + child = g_sequence_get (iter); + + if (!child_is_visible (child)) + continue; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_height_for_width (child, + item_size, + &child_min, &child_nat); + else + gtk_widget_get_preferred_width_for_height (child, + item_size, + &child_min, &child_nat); + + max_min_size = MAX (max_min_size, child_min); + max_nat_size = MAX (max_nat_size, child_nat); + } + + if (min_item_size) + *min_item_size = max_min_size; + + if (nat_item_size) + *nat_item_size = max_nat_size; +} + +/* Gets the largest minimum/natural size on a single line for a given size + * (used to get the largest line heights for a fixed item width and the opposite + * while iterating over a list of children, note the new index is returned) + */ +static GSequenceIter * +get_largest_size_for_line_in_opposing_orientation (GtkFlowBox *box, + GtkOrientation orientation, + GSequenceIter *cursor, + gint line_length, + GtkRequestedSize *item_sizes, + gint extra_pixels, + gint *min_item_size, + gint *nat_item_size) +{ + GSequenceIter *iter; + gint max_min_size = 0; + gint max_nat_size = 0; + gint i; + + i = 0; + for (iter = cursor; + !g_sequence_iter_is_end (iter) && i < line_length; + iter = g_sequence_iter_next (iter)) + { + GtkWidget *child; + gint child_min, child_nat, this_item_size; + + child = g_sequence_get (iter); + + if (!child_is_visible (child)) + continue; + + /* Distribute the extra pixels to the first children in the line + * (could be fancier and spread them out more evenly) */ + this_item_size = item_sizes[i].minimum_size; + if (extra_pixels > 0 && ORIENTATION_ALIGN (box) == GTK_ALIGN_FILL) + { + this_item_size++; + extra_pixels--; + } + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_height_for_width (child, + this_item_size, + &child_min, &child_nat); + else + gtk_widget_get_preferred_width_for_height (child, + this_item_size, + &child_min, &child_nat); + + max_min_size = MAX (max_min_size, child_min); + max_nat_size = MAX (max_nat_size, child_nat); + + i++; + } + + if (min_item_size) + *min_item_size = max_min_size; + + if (nat_item_size) + *nat_item_size = max_nat_size; + + /* Return next item in the list */ + return iter; +} + +/* fit_aligned_item_requests() helper */ +static gint +gather_aligned_item_requests (GtkFlowBox *box, + GtkOrientation orientation, + gint line_length, + gint item_spacing, + gint n_children, + GtkRequestedSize *item_sizes) +{ + GSequenceIter *iter; + gint i; + gint extra_items, natural_line_size = 0; + + extra_items = n_children % line_length; + + i = 0; + for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkWidget *child; + GtkAlign item_align; + gint child_min, child_nat; + gint position; + + child = g_sequence_get (iter); + + if (!child_is_visible (child)) + continue; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_width (child, + &child_min, &child_nat); + else + gtk_widget_get_preferred_height (child, + &child_min, &child_nat); + + /* Get the index and push it over for the last line when spreading to the end */ + position = i % line_length; + + item_align = ORIENTATION_ALIGN (box); + if (item_align == GTK_ALIGN_END && i >= n_children - extra_items) + position += line_length - extra_items; + + /* Round up the size of every column/row */ + item_sizes[position].minimum_size = MAX (item_sizes[position].minimum_size, child_min); + item_sizes[position].natural_size = MAX (item_sizes[position].natural_size, child_nat); + + i++; + } + + for (i = 0; i < line_length; i++) + natural_line_size += item_sizes[i].natural_size; + + natural_line_size += (line_length - 1) * item_spacing; + + return natural_line_size; +} + +static GtkRequestedSize * +fit_aligned_item_requests (GtkFlowBox *box, + GtkOrientation orientation, + gint avail_size, + gint item_spacing, + gint *line_length, /* in-out */ + gint items_per_line, + gint n_children) +{ + GtkRequestedSize *sizes, *try_sizes; + gint try_line_size, try_length; + + sizes = g_new0 (GtkRequestedSize, *line_length); + + /* get the sizes for the initial guess */ + try_line_size = gather_aligned_item_requests (box, + orientation, + *line_length, + item_spacing, + n_children, + sizes); + + /* Try columnizing the whole thing and adding an item to the end of + * the line; try to fit as many columns into the available size as + * possible + */ + for (try_length = *line_length + 1; try_line_size < avail_size; try_length++) + { + try_sizes = g_new0 (GtkRequestedSize, try_length); + try_line_size = gather_aligned_item_requests (box, + orientation, + try_length, + item_spacing, + n_children, + try_sizes); + + if (try_line_size <= avail_size && + items_per_line >= try_length) + { + *line_length = try_length; + + g_free (sizes); + sizes = try_sizes; + } + else + { + /* oops, this one failed; stick to the last size that fit and then return */ + g_free (try_sizes); + break; + } + } + + return sizes; +} + +typedef struct { + GArray *requested; + gint extra_pixels; +} AllocatedLine; + +static gint +get_offset_pixels (GtkAlign align, + gint pixels) +{ + gint offset; + + switch (align) { + case GTK_ALIGN_START: + case GTK_ALIGN_FILL: + offset = 0; + break; + case GTK_ALIGN_CENTER: + offset = pixels / 2; + break; + case GTK_ALIGN_END: + offset = pixels; + break; + default: + g_assert_not_reached (); + break; + } + + return offset; +} + +static void +gtk_flow_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + GtkAllocation child_allocation; + gint avail_size, avail_other_size, min_items, item_spacing, line_spacing; + GtkAlign item_align; + GtkAlign line_align; + GdkWindow *window; + GtkRequestedSize *line_sizes = NULL; + GtkRequestedSize *item_sizes = NULL; + gint min_item_size, nat_item_size; + gint line_length; + gint item_size = 0; + gint line_size = 0, min_fixed_line_size = 0, nat_fixed_line_size = 0; + gint line_offset, item_offset, n_children, n_lines, line_count; + gint extra_pixels = 0, extra_per_item = 0, extra_extra = 0; + gint extra_line_pixels = 0, extra_per_line = 0, extra_line_extra = 0; + gint i, this_line_size; + GSequenceIter *iter; + + child_allocation.x = 0; + child_allocation.y = 0; + child_allocation.width = 0; + child_allocation.height = 0; + + gtk_widget_set_allocation (widget, allocation); + window = gtk_widget_get_window (widget); + if (window != NULL) + gdk_window_move_resize (window, + allocation->x, allocation->y, + allocation->width, allocation->height); + + child_allocation.x = 0; + child_allocation.y = 0; + child_allocation.width = allocation->width; + + min_items = MAX (1, priv->min_children_per_line); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + avail_size = allocation->width; + avail_other_size = allocation->height; + item_spacing = priv->column_spacing; line_spacing = priv->row_spacing; + } + else /* GTK_ORIENTATION_VERTICAL */ + { + avail_size = allocation->height; + avail_other_size = allocation->width; + item_spacing = priv->row_spacing; + line_spacing = priv->column_spacing; + } + + item_align = ORIENTATION_ALIGN (box); + line_align = OPPOSING_ORIENTATION_ALIGN (box); + + /* Get how many lines we'll be needing to flow */ + n_children = get_visible_children (box); + if (n_children <= 0) + return; + + /* + * Deal with ALIGNED/HOMOGENEOUS modes first, start with + * initial guesses at item/line sizes + */ + get_max_item_size (box, priv->orientation, &min_item_size, &nat_item_size); + if (nat_item_size <= 0) + return; + + /* By default flow at the natural item width */ + line_length = avail_size / (nat_item_size + item_spacing); + + /* After the above aproximation, check if we cant fit one more on the line */ + if (line_length * item_spacing + (line_length + 1) * nat_item_size <= avail_size) + line_length++; + + /* Its possible we were allocated just less than the natural width of the + * minimum item flow length */ + line_length = MAX (min_items, line_length); + line_length = MIN (line_length, priv->max_children_per_line); + + /* Here we just use the largest height-for-width and use that for the height + * of all lines */ + if (priv->homogeneous) + { + n_lines = n_children / line_length; + if ((n_children % line_length) > 0) + n_lines++; + + n_lines = MAX (n_lines, 1); + + /* Now we need the real item allocation size */ + item_size = (avail_size - (line_length - 1) * item_spacing) / line_length; + + /* Cut out the expand space if we're not distributing any */ + if (item_align != GTK_ALIGN_FILL) + item_size = MIN (item_size, nat_item_size); + + get_largest_size_for_opposing_orientation (box, + priv->orientation, + item_size, + &min_fixed_line_size, + &nat_fixed_line_size); + + /* resolve a fixed 'line_size' */ + line_size = (avail_other_size - (n_lines - 1) * line_spacing) / n_lines; + + if (line_align != GTK_ALIGN_FILL) + line_size = MIN (line_size, nat_fixed_line_size); + + /* Get the real extra pixels incase of GTK_ALIGN_START lines */ + extra_pixels = avail_size - (line_length - 1) * item_spacing - item_size * line_length; + extra_line_pixels = avail_other_size - (n_lines - 1) * line_spacing - line_size * n_lines; + } + else + { + gboolean first_line = TRUE; + + /* Find the amount of columns that can fit aligned into the available space + * and collect their requests. + */ + item_sizes = fit_aligned_item_requests (box, + priv->orientation, + avail_size, + item_spacing, + &line_length, + priv->max_children_per_line, + n_children); + + /* Calculate the number of lines after determining the final line_length */ + n_lines = n_children / line_length; + if ((n_children % line_length) > 0) + n_lines++; + + n_lines = MAX (n_lines, 1); + line_sizes = g_new0 (GtkRequestedSize, n_lines); + + /* Get the available remaining size */ + avail_size -= (line_length - 1) * item_spacing; + for (i = 0; i < line_length; i++) + avail_size -= item_sizes[i].minimum_size; + + /* Perform a natural allocation on the columnized items and get the remaining pixels */ + if (avail_size > 0) + extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes); + + /* Now that we have the size of each column of items find the size of each individual + * line based on the aligned item sizes. + */ + + for (i = 0, iter = g_sequence_get_begin_iter (priv->children); + !g_sequence_iter_is_end (iter) && i < n_lines; + i++) + { + iter = get_largest_size_for_line_in_opposing_orientation (box, + priv->orientation, + iter, + line_length, + item_sizes, + extra_pixels, + &line_sizes[i].minimum_size, + &line_sizes[i].natural_size); + + + /* Its possible a line is made of completely invisible children */ + if (line_sizes[i].natural_size > 0) + { + if (first_line) + first_line = FALSE; + else + avail_other_size -= line_spacing; + + avail_other_size -= line_sizes[i].minimum_size; + + line_sizes[i].data = GINT_TO_POINTER (i); + } + } + + /* Distribute space among lines naturally */ + if (avail_other_size > 0) + extra_line_pixels = gtk_distribute_natural_allocation (avail_other_size, n_lines, line_sizes); + } + + /* + * Initial sizes of items/lines guessed at this point, + * go on to distribute expand space if needed. + */ + + priv->cur_children_per_line = line_length; + + /* FIXME: This portion needs to consider which columns + * and rows asked for expand space and distribute those + * accordingly for the case of ALIGNED allocation. + * + * If at least one child in a column/row asked for expand; + * we should make that row/column expand entirely. + */ + + /* Calculate expand space per item */ + if (item_align == GTK_ALIGN_FILL) + { + extra_per_item = extra_pixels / line_length; + extra_extra = extra_pixels % line_length; + } + + /* Calculate expand space per line */ + if (line_align == GTK_ALIGN_FILL) + { + extra_per_line = extra_line_pixels / n_lines; + extra_line_extra = extra_line_pixels % n_lines; + } + + /* + * Prepare item/line initial offsets and jump into the + * real allocation loop. + */ + line_offset = item_offset = 0; + + /* prepend extra space to item_offset/line_offset for SPREAD_END */ + item_offset += get_offset_pixels (item_align, extra_pixels); + line_offset += get_offset_pixels (line_align, extra_line_pixels); + + /* Get the allocation size for the first line */ + if (priv->homogeneous) + this_line_size = line_size; + else + { + this_line_size = line_sizes[0].minimum_size; + + if (line_align == GTK_ALIGN_FILL) + { + this_line_size += extra_per_line; + + if (extra_line_extra > 0) + this_line_size++; + } + } + + i = 0; + line_count = 0; + for (iter = g_sequence_get_begin_iter (priv->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkWidget *child; + gint position; + gint this_item_size; + + child = g_sequence_get (iter); + + if (!child_is_visible (child)) + continue; + + /* Get item position */ + position = i % line_length; + + /* adjust the line_offset/count at the beginning of each new line */ + if (i > 0 && position == 0) + { + /* Push the line_offset */ + line_offset += this_line_size + line_spacing; + + line_count++; + + /* Get the new line size */ + if (priv->homogeneous) + this_line_size = line_size; + else + { + this_line_size = line_sizes[line_count].minimum_size; + + if (line_align == GTK_ALIGN_FILL) + { + this_line_size += extra_per_line; + + if (line_count < extra_line_extra) + this_line_size++; + } + } + + item_offset = 0; + + if (item_align == GTK_ALIGN_CENTER) + { + item_offset += get_offset_pixels (item_align, extra_pixels); + } + else if (item_align == GTK_ALIGN_END) + { + item_offset += get_offset_pixels (item_align, extra_pixels); + + /* If we're on the last line, prepend the space for + * any leading items */ + if (line_count == n_lines -1) + { + gint extra_items = n_children % line_length; + + if (priv->homogeneous) + { + item_offset += item_size * (line_length - extra_items); + item_offset += item_spacing * (line_length - extra_items); + } + else + { + gint j; + + for (j = 0; j < (line_length - extra_items); j++) + { + item_offset += item_sizes[j].minimum_size; + item_offset += item_spacing; + } + } + } + } + } + + /* Push the index along for the last line when spreading to the end */ + if (item_align == GTK_ALIGN_END && line_count == n_lines -1) + { + gint extra_items = n_children % line_length; + + position += line_length - extra_items; + } + + if (priv->homogeneous) + this_item_size = item_size; + else + this_item_size = item_sizes[position].minimum_size; + + if (item_align == GTK_ALIGN_FILL) + { + this_item_size += extra_per_item; + + if (position < extra_extra) + this_item_size++; + } + + /* Do the actual allocation */ + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + child_allocation.x = item_offset; + child_allocation.y = line_offset; + child_allocation.width = this_item_size; + child_allocation.height = this_line_size; + } + else /* GTK_ORIENTATION_VERTICAL */ + { + child_allocation.x = line_offset; + child_allocation.y = item_offset; + child_allocation.width = this_line_size; + child_allocation.height = this_item_size; + } + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width; + gtk_widget_size_allocate (child, &child_allocation); + + item_offset += this_item_size; + item_offset += item_spacing; + + i++; + } + + g_free (item_sizes); + g_free (line_sizes); +} + +static GtkSizeRequestMode +gtk_flow_box_get_request_mode (GtkWidget *widget) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + + return (BOX_PRIV (box)->orientation == GTK_ORIENTATION_HORIZONTAL) ? + GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH : GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT; +} + +/* Gets the largest minimum and natural length of + * 'line_length' consecutive items when aligned into rows/columns */ +static void +get_largest_aligned_line_length (GtkFlowBox *box, + GtkOrientation orientation, + gint line_length, + gint *min_size, + gint *nat_size) +{ + GSequenceIter *iter; + gint max_min_size = 0; + gint max_nat_size = 0; + gint spacing, i; + GtkRequestedSize *aligned_item_sizes; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + spacing = BOX_PRIV (box)->column_spacing; + else + spacing = BOX_PRIV (box)->row_spacing; + + aligned_item_sizes = g_new0 (GtkRequestedSize, line_length); + + /* Get the largest sizes of each index in the line. + */ + i = 0; + for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkWidget *child; + gint child_min, child_nat; + + child = g_sequence_get (iter); + if (!child_is_visible (child)) + continue; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_width (child, + &child_min, &child_nat); + else /* GTK_ORIENTATION_VERTICAL */ + gtk_widget_get_preferred_height (child, + &child_min, &child_nat); + + aligned_item_sizes[i % line_length].minimum_size = + MAX (aligned_item_sizes[i % line_length].minimum_size, child_min); + + aligned_item_sizes[i % line_length].natural_size = + MAX (aligned_item_sizes[i % line_length].natural_size, child_nat); + + i++; + } + + /* Add up the largest indexes */ + for (i = 0; i < line_length; i++) + { + max_min_size += aligned_item_sizes[i].minimum_size; + max_nat_size += aligned_item_sizes[i].natural_size; + } + + g_free (aligned_item_sizes); + + max_min_size += (line_length - 1) * spacing; + max_nat_size += (line_length - 1) * spacing; + + if (min_size) + *min_size = max_min_size; + + if (nat_size) + *nat_size = max_nat_size; +} + + +static void +gtk_flow_box_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + gint min_item_width, nat_item_width; + gint min_items, nat_items; + gint min_width, nat_width; + + min_items = MAX (1, priv->min_children_per_line); + nat_items = MAX (min_items, priv->max_children_per_line); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + min_width = nat_width = 0; + + if (!priv->homogeneous) + { + /* When not homogeneous; horizontally oriented boxes + * need enough width for the widest row */ + if (min_items == 1) + { + get_max_item_size (box, + GTK_ORIENTATION_HORIZONTAL, + &min_item_width, + &nat_item_width); + + min_width += min_item_width; + nat_width += nat_item_width; + } + else + { + gint min_line_length, nat_line_length; + + get_largest_aligned_line_length (box, + GTK_ORIENTATION_HORIZONTAL, + min_items, + &min_line_length, + &nat_line_length); + + if (nat_items > min_items) + get_largest_aligned_line_length (box, + GTK_ORIENTATION_HORIZONTAL, + nat_items, + NULL, + &nat_line_length); + + min_width += min_line_length; + nat_width += nat_line_length; + } + } + else /* In homogeneous mode; horizontally oriented boxs + * give the same width to all children */ + { + get_max_item_size (box, GTK_ORIENTATION_HORIZONTAL, + &min_item_width, &nat_item_width); + + min_width += min_item_width * min_items; + min_width += (min_items -1) * priv->column_spacing; + + nat_width += nat_item_width * nat_items; + nat_width += (nat_items -1) * priv->column_spacing; + } + } + else /* GTK_ORIENTATION_VERTICAL */ + { + /* Return the width for the minimum height */ + gint min_height; + + GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, NULL); + GTK_WIDGET_GET_CLASS (widget)->get_preferred_width_for_height (widget, + min_height, + &min_width, + &nat_width); + + } + + if (minimum_size) + *minimum_size = min_width; + + if (natural_size) + *natural_size = nat_width; +} + +static void +gtk_flow_box_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + gint min_item_height, nat_item_height; + gint min_items, nat_items; + gint min_height, nat_height; + + min_items = MAX (1, priv->min_children_per_line); + nat_items = MAX (min_items, priv->max_children_per_line); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + /* Return the height for the minimum width */ + gint min_width; + + GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL); + GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, + min_width, + &min_height, + &nat_height); + } + else /* GTK_ORIENTATION_VERTICAL */ + { + min_height = nat_height = 0; + + if (! priv->homogeneous) + { + /* When not homogeneous; vertically oriented boxes + * need enough height for the tallest column */ + if (min_items == 1) + { + get_max_item_size (box, GTK_ORIENTATION_VERTICAL, + &min_item_height, &nat_item_height); + + min_height += min_item_height; + nat_height += nat_item_height; + } + else + { + gint min_line_length, nat_line_length; + + get_largest_aligned_line_length (box, + GTK_ORIENTATION_VERTICAL, + min_items, + &min_line_length, + &nat_line_length); + + if (nat_items > min_items) + get_largest_aligned_line_length (box, + GTK_ORIENTATION_VERTICAL, + nat_items, + NULL, + &nat_line_length); + + min_height += min_line_length; + nat_height += nat_line_length; + } + + } + else + { + /* In homogeneous mode; vertically oriented boxes + * give the same height to all children + */ + get_max_item_size (box, + GTK_ORIENTATION_VERTICAL, + &min_item_height, + &nat_item_height); + + min_height += min_item_height * min_items; + min_height += (min_items -1) * priv->row_spacing; + + nat_height += nat_item_height * nat_items; + nat_height += (nat_items -1) * priv->row_spacing; + } + } + + if (minimum_size) + *minimum_size = min_height; + + if (natural_size) + *natural_size = nat_height; +} + +static void +gtk_flow_box_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + gint min_item_width, nat_item_width; + gint min_items; + gint min_height, nat_height; + gint avail_size, n_children; + + min_items = MAX (1, priv->min_children_per_line); + + min_height = 0; + nat_height = 0; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + gint min_width; + gint line_length; + gint item_size, extra_pixels; + + n_children = get_visible_children (box); + if (n_children <= 0) + goto out; + + /* Make sure its no smaller than the minimum */ + GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL); + + avail_size = MAX (width, min_width); + if (avail_size <= 0) + goto out; + + get_max_item_size (box, GTK_ORIENTATION_HORIZONTAL, &min_item_width, &nat_item_width); + if (nat_item_width <= 0) + goto out; + + /* By default flow at the natural item width */ + line_length = avail_size / (nat_item_width + priv->column_spacing); + + /* After the above aproximation, check if we cant fit one more on the line */ + if (line_length * priv->column_spacing + (line_length + 1) * nat_item_width <= avail_size) + line_length++; + + /* Its possible we were allocated just less than the natural width of the + * minimum item flow length + */ + line_length = MAX (min_items, line_length); + line_length = MIN (line_length, priv->max_children_per_line); + + /* Now we need the real item allocation size */ + item_size = (avail_size - (line_length - 1) * priv->column_spacing) / line_length; + + /* Cut out the expand space if we're not distributing any */ + if (gtk_widget_get_halign (widget) != GTK_ALIGN_FILL) + { + item_size = MIN (item_size, nat_item_width); + extra_pixels = 0; + } + else + /* Collect the extra pixels for expand children */ + extra_pixels = (avail_size - (line_length - 1) * priv->column_spacing) % line_length; + + if (priv->homogeneous) + { + gint min_item_height, nat_item_height; + gint lines; + + /* Here we just use the largest height-for-width and + * add up the size accordingly + */ + get_largest_size_for_opposing_orientation (box, + GTK_ORIENTATION_HORIZONTAL, + item_size, + &min_item_height, + &nat_item_height); + + /* Round up how many lines we need to allocate for */ + lines = n_children / line_length; + if ((n_children % line_length) > 0) + lines++; + + min_height = min_item_height * lines; + nat_height = nat_item_height * lines; + + min_height += (lines - 1) * priv->row_spacing; + nat_height += (lines - 1) * priv->row_spacing; + } + else + { + gint min_line_height, nat_line_height, i; + gboolean first_line = TRUE; + GtkRequestedSize *item_sizes; + GSequenceIter *iter; + + /* First get the size each set of items take to span the line + * when aligning the items above and below after flowping. + */ + item_sizes = fit_aligned_item_requests (box, + priv->orientation, + avail_size, + priv->column_spacing, + &line_length, + priv->max_children_per_line, + n_children); + + /* Get the available remaining size */ + avail_size -= (line_length - 1) * priv->column_spacing; + for (i = 0; i < line_length; i++) + avail_size -= item_sizes[i].minimum_size; + + if (avail_size > 0) + extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes); + + for (iter = g_sequence_get_begin_iter (priv->children); + !g_sequence_iter_is_end (iter);) + { + iter = get_largest_size_for_line_in_opposing_orientation (box, + GTK_ORIENTATION_HORIZONTAL, + iter, + line_length, + item_sizes, + extra_pixels, + &min_line_height, + &nat_line_height); + /* Its possible the line only had invisible widgets */ + if (nat_line_height > 0) + { + if (first_line) + first_line = FALSE; + else + { + min_height += priv->row_spacing; + nat_height += priv->row_spacing; + } + + min_height += min_line_height; + nat_height += nat_line_height; + } + } + + g_free (item_sizes); + } + } + else /* GTK_ORIENTATION_VERTICAL */ + { + /* Return the minimum height */ + GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, &nat_height); + } + + out: + + if (minimum_height) + *minimum_height = min_height; + + if (natural_height) + *natural_height = nat_height; +} + +static void +gtk_flow_box_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + gint min_item_height, nat_item_height; + gint min_items; + gint min_width, nat_width; + gint avail_size, n_children; + + min_items = MAX (1, priv->min_children_per_line); + + min_width = 0; + nat_width = 0; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + /* Return the minimum width */ + GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, &nat_width); + } + else /* GTK_ORIENTATION_VERTICAL */ + { + gint min_height; + gint line_length; + gint item_size, extra_pixels; + + n_children = get_visible_children (box); + if (n_children <= 0) + goto out; + + /* Make sure its no smaller than the minimum */ + GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, NULL); + + avail_size = MAX (height, min_height); + if (avail_size <= 0) + goto out; + + get_max_item_size (box, GTK_ORIENTATION_VERTICAL, &min_item_height, &nat_item_height); + + /* By default flow at the natural item width */ + line_length = avail_size / (nat_item_height + priv->row_spacing); + + /* After the above aproximation, check if we cant fit one more on the line */ + if (line_length * priv->row_spacing + (line_length + 1) * nat_item_height <= avail_size) + line_length++; + + /* Its possible we were allocated just less than the natural width of the + * minimum item flow length + */ + line_length = MAX (min_items, line_length); + line_length = MIN (line_length, priv->max_children_per_line); + + /* Now we need the real item allocation size */ + item_size = (avail_size - (line_length - 1) * priv->row_spacing) / line_length; + + /* Cut out the expand space if we're not distributing any */ + if (gtk_widget_get_valign (widget) != GTK_ALIGN_FILL) + { + item_size = MIN (item_size, nat_item_height); + extra_pixels = 0; + } + else + /* Collect the extra pixels for expand children */ + extra_pixels = (avail_size - (line_length - 1) * priv->row_spacing) % line_length; + + if (priv->homogeneous) + { + gint min_item_width, nat_item_width; + gint lines; + + /* Here we just use the largest height-for-width and + * add up the size accordingly + */ + get_largest_size_for_opposing_orientation (box, + GTK_ORIENTATION_VERTICAL, + item_size, + &min_item_width, + &nat_item_width); + + /* Round up how many lines we need to allocate for */ + n_children = get_visible_children (box); + lines = n_children / line_length; + if ((n_children % line_length) > 0) + lines++; + + min_width = min_item_width * lines; + nat_width = nat_item_width * lines; + + min_width += (lines - 1) * priv->column_spacing; + nat_width += (lines - 1) * priv->column_spacing; + } + else + { + gint min_line_width, nat_line_width, i; + gboolean first_line = TRUE; + GtkRequestedSize *item_sizes; + GSequenceIter *iter; + + /* First get the size each set of items take to span the line + * when aligning the items above and below after flowping. + */ + item_sizes = fit_aligned_item_requests (box, + priv->orientation, + avail_size, + priv->row_spacing, + &line_length, + priv->max_children_per_line, + n_children); + + /* Get the available remaining size */ + avail_size -= (line_length - 1) * priv->column_spacing; + for (i = 0; i < line_length; i++) + avail_size -= item_sizes[i].minimum_size; + + if (avail_size > 0) + extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes); + + for (iter = g_sequence_get_begin_iter (priv->children); + !g_sequence_iter_is_end (iter);) + { + iter = get_largest_size_for_line_in_opposing_orientation (box, + GTK_ORIENTATION_VERTICAL, + iter, + line_length, + item_sizes, + extra_pixels, + &min_line_width, + &nat_line_width); + + /* Its possible the last line only had invisible widgets */ + if (nat_line_width > 0) + { + if (first_line) + first_line = FALSE; + else + { + min_width += priv->column_spacing; + nat_width += priv->column_spacing; + } + + min_width += min_line_width; + nat_width += nat_line_width; + } + } + g_free (item_sizes); + } + } + + out: + if (minimum_width) + *minimum_width = min_width; + + if (natural_width) + *natural_width = nat_width; +} + +/* Drawing {{{3 */ + +static gboolean +gtk_flow_box_draw (GtkWidget *widget, + cairo_t *cr) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + GtkAllocation allocation = { 0, }; + GtkStyleContext* context; + + gtk_widget_get_allocation (GTK_WIDGET (box), &allocation); + context = gtk_widget_get_style_context (GTK_WIDGET (box)); + gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height); + + GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->draw (widget, cr); + + if (priv->rubberband_first && priv->rubberband_last) + { + GSequenceIter *iter, *iter1, *iter2; + GdkRectangle line_rect, rect; + GArray *lines; + gboolean vertical; + + vertical = priv->orientation == GTK_ORIENTATION_VERTICAL; + + cairo_save (cr); + + context = gtk_widget_get_style_context (widget); + gtk_style_context_save (context); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND); + + iter1 = CHILD_PRIV (priv->rubberband_first)->iter; + iter2 = CHILD_PRIV (priv->rubberband_last)->iter; + + if (g_sequence_iter_compare (iter2, iter1) < 0) + { + iter = iter1; + iter1 = iter2; + iter2 = iter; + } + + line_rect.width = 0; + lines = g_array_new (FALSE, FALSE, sizeof (GdkRectangle)); + + for (iter = iter1; + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkWidget *child; + + child = g_sequence_get (iter); + gtk_widget_get_allocation (GTK_WIDGET (child), &rect); + if (line_rect.width == 0) + line_rect = rect; + else + { + if ((vertical && rect.x == line_rect.x) || + (!vertical && rect.y == line_rect.y)) + gdk_rectangle_union (&rect, &line_rect, &line_rect); + else + { + g_array_append_val (lines, line_rect); + line_rect = rect; + } + } + + if (g_sequence_iter_compare (iter, iter2) == 0) + break; + } + + if (line_rect.width != 0) + g_array_append_val (lines, line_rect); + + if (lines->len > 0) + { + GtkStateFlags state; + cairo_path_t *path; + GtkBorder border; + GdkRGBA border_color; + + if (vertical) + path_from_vertical_line_rects (cr, (GdkRectangle *)lines->data, lines->len); + else + path_from_horizontal_line_rects (cr, (GdkRectangle *)lines->data, lines->len); + + /* For some reason we need to copy and reapply the path, + * or it gets eaten by gtk_render_background() + */ + path = cairo_copy_path (cr); + + cairo_save (cr); + cairo_clip (cr); + gtk_widget_get_allocation (widget, &allocation); + gtk_render_background (context, cr, + 0, 0, + allocation.width, allocation.height); + cairo_restore (cr); + + cairo_append_path (cr, path); + cairo_path_destroy (path); + + state = gtk_widget_get_state_flags (widget); + gtk_style_context_get_border_color (context, state, &border_color); + gtk_style_context_get_border (context, state, &border); + + cairo_set_line_width (cr, border.left); + gdk_cairo_set_source_rgba (cr, &border_color); + cairo_stroke (cr); + } + g_array_free (lines, TRUE); + + gtk_style_context_restore (context); + cairo_restore (cr); + } + + return TRUE; +} + +/* Autoscrolling {{{3 */ + +static void +remove_autoscroll (GtkFlowBox *box) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + + if (priv->autoscroll_id) + { + gtk_widget_remove_tick_callback (GTK_WIDGET (box), priv->autoscroll_id); + priv->autoscroll_id = 0; + } + + priv->autoscroll_mode = GTK_SCROLL_NONE; +} + +static gboolean +autoscroll_cb (GtkWidget *widget, + GdkFrameClock *frame_clock, + gpointer data) +{ + GtkFlowBox *box = GTK_FLOW_BOX (data); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + GtkAdjustment *adjustment; + gdouble factor; + gdouble increment; + gdouble value; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + adjustment = priv->vadjustment; + else + adjustment = priv->hadjustment; + + switch (priv->autoscroll_mode) + { + case GTK_SCROLL_STEP_FORWARD: + factor = AUTOSCROLL_FACTOR; + break; + case GTK_SCROLL_STEP_BACKWARD: + factor = - AUTOSCROLL_FACTOR; + break; + case GTK_SCROLL_PAGE_FORWARD: + factor = AUTOSCROLL_FACTOR_FAST; + break; + case GTK_SCROLL_PAGE_BACKWARD: + factor = - AUTOSCROLL_FACTOR_FAST; + break; + default: + g_assert_not_reached (); + } + + increment = gtk_adjustment_get_step_increment (adjustment) / factor; + + value = gtk_adjustment_get_value (adjustment); + value += increment; + gtk_adjustment_set_value (adjustment, value); + + if (priv->rubberband_select) + { + gint x, y; + GtkFlowBoxChild *child; + + gdk_window_get_device_position (gtk_widget_get_window (widget), + priv->rubberband_device, + &x, &y, NULL); + + child = gtk_flow_box_find_child_at_pos (box, x, y); + + gtk_flow_box_update_prelight (box, child); + gtk_flow_box_update_active (box, child); + + if (child != NULL) + priv->rubberband_last = child; + } + + return G_SOURCE_CONTINUE; +} + +static void +add_autoscroll (GtkFlowBox *box) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + + if (priv->autoscroll_id != 0 || + priv->autoscroll_mode == GTK_SCROLL_NONE) + return; + + priv->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (box), + (GtkTickCallback)autoscroll_cb, + box, + NULL); +} + +static gboolean +get_view_rect (GtkFlowBox *box, + GdkRectangle *rect) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + GtkWidget *parent; + GdkWindow *view; + + parent = gtk_widget_get_parent (GTK_WIDGET (box)); + if (GTK_IS_VIEWPORT (parent)) + { + view = gtk_viewport_get_view_window (GTK_VIEWPORT (parent)); + rect->x = gtk_adjustment_get_value (priv->hadjustment); + rect->y = gtk_adjustment_get_value (priv->vadjustment); + rect->width = gdk_window_get_width (view); + rect->height = gdk_window_get_height (view); + return TRUE; + } + + return FALSE; +} + +static void +update_autoscroll_mode (GtkFlowBox *box, + gint x, + gint y) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + GtkScrollType mode = GTK_SCROLL_NONE; + GdkRectangle rect; + gint size, pos; + + if (priv->rubberband_select && get_view_rect (box, &rect)) + { + if (priv->orientation == GTK_ORIENTATION_VERTICAL) + { + size = rect.width; + pos = x - rect.x; + } + else + { + size = rect.height; + pos = y - rect.y; + } + + if (pos < 0 - AUTOSCROLL_FAST_DISTANCE) + mode = GTK_SCROLL_PAGE_BACKWARD; + else if (pos > size + AUTOSCROLL_FAST_DISTANCE) + mode = GTK_SCROLL_PAGE_FORWARD; + else if (pos < 0) + mode = GTK_SCROLL_STEP_BACKWARD; + else if (pos > size) + mode = GTK_SCROLL_STEP_FORWARD; + } + + if (mode != priv->autoscroll_mode) + { + remove_autoscroll (box); + priv->autoscroll_mode = mode; + add_autoscroll (box); + } +} + +/* Event handling {{{3 */ + +static gboolean +gtk_flow_box_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxChild *child; + + if (event->window != gtk_widget_get_window (GTK_WIDGET (box))) + return FALSE; + + child = gtk_flow_box_find_child_at_pos (box, event->x, event->y); + gtk_flow_box_update_prelight (box, child); + gtk_flow_box_update_active (box, child); + + return FALSE; +} + +static gboolean +gtk_flow_box_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxChild *child = NULL; + + if (event->window != gtk_widget_get_window (GTK_WIDGET (box))) + return FALSE; + + if (event->detail != GDK_NOTIFY_INFERIOR) + child = NULL; + else + child = gtk_flow_box_find_child_at_pos (box, event->x, event->y); + + gtk_flow_box_update_prelight (box, child); + gtk_flow_box_update_active (box, child); + + return FALSE; +} + +static gboolean +gtk_flow_box_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + GtkFlowBoxChild *child; + GdkWindow *window; + GdkWindow *event_window; + gint relative_x; + gint relative_y; + gdouble parent_x; + gdouble parent_y; + + window = gtk_widget_get_window (GTK_WIDGET (box)); + event_window = event->window; + relative_x = event->x; + relative_y = event->y; + + while ((event_window != NULL) && (event_window != window)) + { + gdk_window_coords_to_parent (event_window, + relative_x, relative_y, + &parent_x, &parent_y); + relative_x = parent_x; + relative_y = parent_y; + event_window = gdk_window_get_effective_parent (event_window); + } + + child = gtk_flow_box_find_child_at_pos (box, relative_x, relative_y); + gtk_flow_box_update_prelight (box, child); + gtk_flow_box_update_active (box, child); + + if (priv->track_motion) + { + if (!priv->rubberband_select && + (event->x - priv->button_down_x) * (event->x - priv->button_down_x) + + (event->y - priv->button_down_y) * (event->y - priv->button_down_y) > RUBBERBAND_START_DISTANCE * RUBBERBAND_START_DISTANCE) + { + priv->rubberband_select = TRUE; + priv->rubberband_first = gtk_flow_box_find_child_at_pos (box, priv->button_down_x, priv->button_down_y); + } + + if (priv->rubberband_select) + { + if (priv->rubberband_first == NULL) + priv->rubberband_first = child; + if (child != NULL) + priv->rubberband_last = child; + + update_autoscroll_mode (box, event->x, event->y); + } + } + + return FALSE; +} + +static gboolean +gtk_flow_box_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + GtkFlowBoxChild *child; + + if (event->button == GDK_BUTTON_PRIMARY) + { + child = gtk_flow_box_find_child_at_pos (box, event->x, event->y); + if (child != NULL) + { + priv->active_child = child; + priv->active_child_active = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (box)); + if (event->type == GDK_2BUTTON_PRESS && + !priv->activate_on_single_click) + { + g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child); + return TRUE; + } + } + + if (priv->selection_mode == GTK_SELECTION_MULTIPLE) + { + priv->track_motion = TRUE; + priv->rubberband_select = FALSE; + priv->rubberband_first = NULL; + priv->rubberband_last = NULL; + priv->button_down_x = event->x; + priv->button_down_y = event->y; + priv->rubberband_device = gdk_event_get_device ((GdkEvent*)event); + } + } + + return FALSE; +} + +static gboolean +gtk_flow_box_button_release_event (GtkWidget *widget, + GdkEventButton *event) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + gboolean modify_selection; + gboolean extend_selection; + + if (event->button == GDK_BUTTON_PRIMARY) + { + if (priv->active_child != NULL && priv->active_child_active) + { + get_current_selection_modifiers (GTK_WIDGET (box), &modify_selection, &extend_selection); + + if (priv->activate_on_single_click) + gtk_flow_box_select_and_activate (box, priv->active_child); + else + gtk_flow_box_update_selection (box, priv->active_child, TRUE, extend_selection); + } + + priv->active_child = NULL; + priv->active_child_active = FALSE; + gtk_widget_queue_draw (GTK_WIDGET (box)); + } + + remove_autoscroll (box); + priv->track_motion = FALSE; + if (priv->rubberband_select) + { + gtk_flow_box_select_all_between (box, priv->rubberband_first, priv->rubberband_last); + priv->rubberband_first = NULL; + priv->rubberband_last = NULL; + priv->rubberband_select = FALSE; + priv->rubberband_device = NULL; + } + + return FALSE; +} + +/* Realize and map {{{3 */ + +static void +gtk_flow_box_realize (GtkWidget *widget) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkAllocation allocation; + GdkWindowAttr attributes = {0}; + GdkWindow *window; + + gtk_widget_get_allocation (GTK_WIDGET (box), &allocation); + gtk_widget_set_realized (GTK_WIDGET (box), TRUE); + + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (box)) + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_POINTER_MOTION_MASK + | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK; + attributes.wclass = GDK_INPUT_OUTPUT; + + window = gdk_window_new (gtk_widget_get_parent_window (GTK_WIDGET (box)), + &attributes, GDK_WA_X | GDK_WA_Y); + gtk_style_context_set_background (gtk_widget_get_style_context (GTK_WIDGET (box)), window); + gtk_widget_register_window (GTK_WIDGET (box), window); + gtk_widget_set_window (GTK_WIDGET (box), window); +} + +static void +gtk_flow_box_unmap (GtkWidget *widget) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + + remove_autoscroll (box); + + GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->unmap (widget); +} + +/* GtkContainer implementation {{{2 */ + +static void +gtk_flow_box_add (GtkContainer *container, + GtkWidget *child) +{ + gtk_flow_box_insert (GTK_FLOW_BOX (container), child, -1); +} + +static void +gtk_flow_box_remove (GtkContainer *container, + GtkWidget *widget) +{ + GtkFlowBox *box = GTK_FLOW_BOX (container); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + gboolean was_visible; + gboolean was_selected; + GtkFlowBoxChild *child; + + if (GTK_IS_FLOW_BOX_CHILD (widget)) + child = GTK_FLOW_BOX_CHILD (widget); + else + { + child = (GtkFlowBoxChild*)gtk_widget_get_parent (widget); + if (!GTK_IS_FLOW_BOX_CHILD (child)) + { + g_warning ("Tried to remove non-child %p\n", widget); + return; + } + } + + was_visible = child_is_visible (GTK_WIDGET (child)); + was_selected = CHILD_PRIV (child)->selected; + + if (child == priv->prelight_child) + priv->prelight_child = NULL; + if (child == priv->active_child) + priv->active_child = NULL; + if (child == priv->selected_child) + priv->selected_child = NULL; + + gtk_widget_unparent (GTK_WIDGET (child)); + g_sequence_remove (CHILD_PRIV (child)->iter); + + if (was_visible && gtk_widget_get_visible (GTK_WIDGET (box))) + gtk_widget_queue_resize (GTK_WIDGET (box)); + + if (was_selected) + g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0); +} + +static void +gtk_flow_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_target) +{ + GSequenceIter *iter; + GtkWidget *child; + + iter = g_sequence_get_begin_iter (BOX_PRIV (container)->children); + while (!g_sequence_iter_is_end (iter)) + { + child = g_sequence_get (iter); + iter = g_sequence_iter_next (iter); + callback (child, callback_target); + } +} + +static GType +gtk_flow_box_child_type (GtkContainer *container) +{ + return GTK_TYPE_FLOW_BOX_CHILD; +} + +/* Keynav {{{2 */ + +static gboolean +gtk_flow_box_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GtkFlowBox *box = GTK_FLOW_BOX (widget); + GtkWidget *focus_child; + GSequenceIter *iter; + GtkFlowBoxChild *next_focus_child; + + focus_child = gtk_container_get_focus_child (GTK_CONTAINER (box)); + next_focus_child = NULL; + + if (focus_child != NULL) + { + if (gtk_widget_child_focus (focus_child, direction)) + return TRUE; + + iter = CHILD_PRIV (focus_child)->iter; + + if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD) + iter = gtk_flow_box_get_previous_focusable (box, iter); + else if (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD) + iter = gtk_flow_box_get_next_focusable (box, iter); + else if (direction == GTK_DIR_UP) + iter = gtk_flow_box_get_above_focusable (box, iter); + else if (direction == GTK_DIR_DOWN) + iter = gtk_flow_box_get_below_focusable (box, iter); + + if (iter != NULL && !g_sequence_iter_is_end (iter)) + next_focus_child = g_sequence_get (iter); + } + else + { + if (BOX_PRIV (box)->selected_child) + next_focus_child = BOX_PRIV (box)->selected_child; + else + { + if (direction == GTK_DIR_UP || direction == GTK_DIR_TAB_BACKWARD) + iter = gtk_flow_box_get_last_focusable (box); + else + iter = gtk_flow_box_get_first_focusable (box); + + if (iter != NULL && !g_sequence_iter_is_end (iter)) + next_focus_child = g_sequence_get (iter); + } + } + + if (next_focus_child == NULL) + { + if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN || + direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT) + { + if (gtk_widget_keynav_failed (GTK_WIDGET (box), direction)) + return TRUE; + } + + return FALSE; + } + + if (gtk_widget_child_focus (GTK_WIDGET (next_focus_child), direction)) + return TRUE; + + return TRUE; +} + +static void +gtk_flow_box_add_move_binding (GtkBindingSet *binding_set, + guint keyval, + GdkModifierType modmask, + GtkMovementStep step, + gint count) +{ + GdkDisplay *display; + GdkModifierType extend_mod_mask = GDK_SHIFT_MASK; + GdkModifierType modify_mod_mask = GDK_CONTROL_MASK; + + display = gdk_display_get_default (); + if (display) + { + extend_mod_mask = gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + GDK_MODIFIER_INTENT_EXTEND_SELECTION); + modify_mod_mask = gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + GDK_MODIFIER_INTENT_MODIFY_SELECTION); + } + + gtk_binding_entry_add_signal (binding_set, keyval, modmask, + "move-cursor", 2, + GTK_TYPE_MOVEMENT_STEP, step, + G_TYPE_INT, count, + NULL); + gtk_binding_entry_add_signal (binding_set, keyval, modmask | extend_mod_mask, + "move-cursor", 2, + GTK_TYPE_MOVEMENT_STEP, step, + G_TYPE_INT, count, + NULL); + gtk_binding_entry_add_signal (binding_set, keyval, modmask | modify_mod_mask, + "move-cursor", 2, + GTK_TYPE_MOVEMENT_STEP, step, + G_TYPE_INT, count, + NULL); + gtk_binding_entry_add_signal (binding_set, keyval, modmask | extend_mod_mask | modify_mod_mask, + "move-cursor", 2, + GTK_TYPE_MOVEMENT_STEP, step, + G_TYPE_INT, count, + NULL); +} + +static void +gtk_flow_box_activate_cursor_child (GtkFlowBox *box) +{ + gtk_flow_box_select_and_activate (box, BOX_PRIV (box)->cursor_child); +} + +static void +gtk_flow_box_toggle_cursor_child (GtkFlowBox *box) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + + if (priv->cursor_child == NULL) + return; + + if ((priv->selection_mode == GTK_SELECTION_SINGLE || + priv->selection_mode == GTK_SELECTION_MULTIPLE) && + CHILD_PRIV (priv->cursor_child)->selected) + gtk_flow_box_unselect_child_info (box, priv->cursor_child); + else + gtk_flow_box_select_and_activate (box, priv->cursor_child); +} + +static void +gtk_flow_box_move_cursor (GtkFlowBox *box, + GtkMovementStep step, + gint count) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + gboolean modify_selection; + gboolean extend_selection; + GtkFlowBoxChild *child; + GtkFlowBoxChild *prev; + GtkFlowBoxChild *next; + GtkAllocation allocation; + gint page_size; + GSequenceIter *iter; + gint start; + GtkAdjustment *adjustment; + gboolean vertical; + + vertical = priv->orientation == GTK_ORIENTATION_VERTICAL; + + get_current_selection_modifiers (GTK_WIDGET (box), &modify_selection, &extend_selection); + + if (vertical) + { + switch (step) + { + case GTK_MOVEMENT_VISUAL_POSITIONS: + step = GTK_MOVEMENT_DISPLAY_LINES; + break; + case GTK_MOVEMENT_DISPLAY_LINES: + step = GTK_MOVEMENT_VISUAL_POSITIONS; + break; + default: ; + } + } + + child = NULL; + switch (step) + { + case GTK_MOVEMENT_VISUAL_POSITIONS: + if (priv->cursor_child != NULL) + { + iter = CHILD_PRIV (priv->cursor_child)->iter; + if (gtk_widget_get_direction (GTK_WIDGET (box)) == GTK_TEXT_DIR_RTL) + count = - count; + + while (count < 0 && iter != NULL) + { + iter = gtk_flow_box_get_previous_focusable (box, iter); + count = count + 1; + } + while (count > 0 && iter != NULL) + { + iter = gtk_flow_box_get_next_focusable (box, iter); + count = count - 1; + } + + if (iter != NULL && !g_sequence_iter_is_end (iter)) + child = g_sequence_get (iter); + } + break; + + case GTK_MOVEMENT_BUFFER_ENDS: + if (count < 0) + iter = gtk_flow_box_get_first_focusable (box); + else + iter = gtk_flow_box_get_last_focusable (box); + if (iter != NULL && !g_sequence_iter_is_end (iter)) + child = g_sequence_get (iter); + break; + + case GTK_MOVEMENT_DISPLAY_LINES: + if (priv->cursor_child != NULL) + { + iter = CHILD_PRIV (priv->cursor_child)->iter; + + while (count < 0 && iter != NULL) + { + iter = gtk_flow_box_get_above_focusable (box, iter); + count = count + 1; + } + while (count > 0 && iter != NULL) + { + iter = gtk_flow_box_get_below_focusable (box, iter); + count = count - 1; + } + + if (iter != NULL && !g_sequence_iter_is_end (iter)) + child = g_sequence_get (iter); + } + break; + + case GTK_MOVEMENT_PAGES: + page_size = 100; + adjustment = vertical ? priv->hadjustment : priv->vadjustment; + if (adjustment) + page_size = gtk_adjustment_get_page_increment (adjustment); + + if (priv->cursor_child != NULL) + { + child = priv->cursor_child; + iter = CHILD_PRIV (child)->iter; + gtk_widget_get_allocation (GTK_WIDGET (child), &allocation); + start = vertical ? allocation.x : allocation.y; + + if (count < 0) + { + gint i = 0; + + /* Up */ + while (iter != NULL) + { + iter = gtk_flow_box_get_previous_focusable (box, iter); + if (iter == NULL) + break; + + prev = g_sequence_get (iter); + + /* go up an even number of rows */ + if (i % priv->cur_children_per_line == 0) + { + gtk_widget_get_allocation (GTK_WIDGET (prev), &allocation); + if ((vertical ? allocation.x : allocation.y) < start - page_size) + break; + } + + child = prev; + i++; + } + } + else + { + gint i = 0; + + /* Down */ + while (!g_sequence_iter_is_end (iter)) + { + iter = gtk_flow_box_get_next_focusable (box, iter); + if (g_sequence_iter_is_end (iter)) + break; + + next = g_sequence_get (iter); + + if (i % priv->cur_children_per_line == 0) + { + gtk_widget_get_allocation (GTK_WIDGET (next), &allocation); + if ((vertical ? allocation.x : allocation.y) > start + page_size) + break; + } + + child = next; + i++; + } + } + gtk_widget_get_allocation (GTK_WIDGET (child), &allocation); + } + break; + + default: + g_assert_not_reached (); + } + + if (child == NULL || child == priv->cursor_child) + { + GtkDirectionType direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN; + + if (!gtk_widget_keynav_failed (GTK_WIDGET (box), direction)) + { + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box)); + + if (toplevel) + gtk_widget_child_focus (toplevel, + direction == GTK_DIR_UP ? + GTK_DIR_TAB_BACKWARD : + GTK_DIR_TAB_FORWARD); + + } + + return; + } + + gtk_flow_box_update_cursor (box, child); + gtk_flow_box_update_selection (box, child, modify_selection, extend_selection); +} + +/* Selection {{{2 */ + +static void +gtk_flow_box_selected_children_changed (GtkFlowBox *box) +{ + _gtk_flow_box_accessible_selection_changed (GTK_WIDGET (box)); +} + +/* GObject implementation {{{2 */ + +static void +gtk_flow_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkFlowBox *box = GTK_FLOW_BOX (object); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, priv->orientation); + break; + case PROP_HOMOGENEOUS: + g_value_set_boolean (value, priv->homogeneous); + break; + case PROP_COLUMN_SPACING: + g_value_set_uint (value, priv->column_spacing); + break; + case PROP_ROW_SPACING: + g_value_set_uint (value, priv->row_spacing); + break; + case PROP_MIN_CHILDREN_PER_LINE: + g_value_set_uint (value, priv->min_children_per_line); + break; + case PROP_MAX_CHILDREN_PER_LINE: + g_value_set_uint (value, priv->max_children_per_line); + break; + case PROP_SELECTION_MODE: + g_value_set_enum (value, priv->selection_mode); + break; + case PROP_ACTIVATE_ON_SINGLE_CLICK: + g_value_set_boolean (value, priv->activate_on_single_click); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_flow_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkFlowBox *box = GTK_FLOW_BOX (object); + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + + switch (prop_id) + { + case PROP_ORIENTATION: + priv->orientation = g_value_get_enum (value); + /* Re-box the children in the new orientation */ + gtk_widget_queue_resize (GTK_WIDGET (box)); + break; + case PROP_HOMOGENEOUS: + gtk_flow_box_set_homogeneous (box, g_value_get_boolean (value)); + break; + case PROP_COLUMN_SPACING: + gtk_flow_box_set_column_spacing (box, g_value_get_uint (value)); + break; + case PROP_ROW_SPACING: + gtk_flow_box_set_row_spacing (box, g_value_get_uint (value)); + break; + case PROP_MIN_CHILDREN_PER_LINE: + gtk_flow_box_set_min_children_per_line (box, g_value_get_uint (value)); + break; + case PROP_MAX_CHILDREN_PER_LINE: + gtk_flow_box_set_max_children_per_line (box, g_value_get_uint (value)); + break; + case PROP_SELECTION_MODE: + gtk_flow_box_set_selection_mode (box, g_value_get_enum (value)); + break; + case PROP_ACTIVATE_ON_SINGLE_CLICK: + gtk_flow_box_set_activate_on_single_click (box, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_flow_box_finalize (GObject *obj) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (obj); + + if (priv->filter_destroy != NULL) + priv->filter_destroy (priv->filter_data); + if (priv->sort_destroy != NULL) + priv->sort_destroy (priv->sort_data); + + g_sequence_free (priv->children); + g_clear_object (&priv->hadjustment); + g_clear_object (&priv->vadjustment); + + G_OBJECT_CLASS (gtk_flow_box_parent_class)->finalize (obj); +} + +static void +gtk_flow_box_class_init (GtkFlowBoxClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class); + GtkBindingSet *binding_set; + + object_class->finalize = gtk_flow_box_finalize; + object_class->get_property = gtk_flow_box_get_property; + object_class->set_property = gtk_flow_box_set_property; + + widget_class->enter_notify_event = gtk_flow_box_enter_notify_event; + widget_class->leave_notify_event = gtk_flow_box_leave_notify_event; + widget_class->motion_notify_event = gtk_flow_box_motion_notify_event; + widget_class->size_allocate = gtk_flow_box_size_allocate; + widget_class->realize = gtk_flow_box_realize; + widget_class->unmap = gtk_flow_box_unmap; + widget_class->focus = gtk_flow_box_focus; + widget_class->draw = gtk_flow_box_draw; + widget_class->button_press_event = gtk_flow_box_button_press_event; + widget_class->button_release_event = gtk_flow_box_button_release_event; + widget_class->get_request_mode = gtk_flow_box_get_request_mode; + widget_class->get_preferred_width = gtk_flow_box_get_preferred_width; + widget_class->get_preferred_height = gtk_flow_box_get_preferred_height; + widget_class->get_preferred_height_for_width = gtk_flow_box_get_preferred_height_for_width; + widget_class->get_preferred_width_for_height = gtk_flow_box_get_preferred_width_for_height; + + container_class->add = gtk_flow_box_add; + container_class->remove = gtk_flow_box_remove; + container_class->forall = gtk_flow_box_forall; + container_class->child_type = gtk_flow_box_child_type; + gtk_container_class_handle_border_width (container_class); + + class->activate_cursor_child = gtk_flow_box_activate_cursor_child; + class->toggle_cursor_child = gtk_flow_box_toggle_cursor_child; + class->move_cursor = gtk_flow_box_move_cursor; + class->select_all = gtk_flow_box_select_all; + class->unselect_all = gtk_flow_box_unselect_all; + class->selected_children_changed = gtk_flow_box_selected_children_changed; + + g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); + + /** + * GtkFlowBox:selection-mode: + * + * The selection mode used by the flow box. + */ + g_object_class_install_property (object_class, + PROP_SELECTION_MODE, + g_param_spec_enum ("selection-mode", + P_("Selection mode"), + P_("The selection mode"), + GTK_TYPE_SELECTION_MODE, + GTK_SELECTION_SINGLE, + G_PARAM_READWRITE)); + + /** + * GtkFlowBox:activate-on-single-click: + * + * Determines whether children can be activated with a single + * click, or require a double-click. + */ + g_object_class_install_property (object_class, + PROP_ACTIVATE_ON_SINGLE_CLICK, + g_param_spec_boolean ("activate-on-single-click", + P_("Activate on Single Click"), + P_("Activate row on a single click"), + TRUE, + G_PARAM_READWRITE)); + + /** + * GtkFlowBox:homogeneous: + * + * Determines whether all children should be allocated the + * same size. + */ + g_object_class_install_property (object_class, + PROP_HOMOGENEOUS, + g_param_spec_boolean ("homogeneous", + P_("Homogeneous"), + P_("Whether the children should all be the same size"), + FALSE, + G_PARAM_READWRITE)); + + /** + * GtkFlowBox:min-children-per-line: + * + * The minimum number of children to allocate consecutively + * in the given orientation. + * + * Setting the minimum children per line ensures + * that a reasonably small height will be requested + * for the overall minimum width of the box. + */ + g_object_class_install_property (object_class, + PROP_MIN_CHILDREN_PER_LINE, + g_param_spec_uint ("min-children-per-line", + P_("Minimum Children Per Line"), + P_("The minimum number of children to allocate " + "consecutively in the given orientation."), + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE)); + + /** + * GtkFlowBox:max-children-per-line: + * + * The maximum amount of children to request space for consecutively + * in the given orientation. + */ + g_object_class_install_property (object_class, + PROP_MAX_CHILDREN_PER_LINE, + g_param_spec_uint ("max-children-per-line", + P_("Maximum Children Per Line"), + P_("The maximum amount of children to request space for " + "consecutively in the given orientation."), + 0, + G_MAXUINT, + DEFAULT_MAX_CHILDREN_PER_LINE, + G_PARAM_READWRITE)); + + /** + * GtkFlowBox:row-spacing: + * + * The amount of vertical space between two children. + */ + g_object_class_install_property (object_class, + PROP_ROW_SPACING, + g_param_spec_uint ("row-spacing", + P_("Vertical spacing"), + P_("The amount of vertical space between two children"), + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE)); + + /** + * GtkFlowBox:column-spacing: + * + * The amount of horizontal space between two children. + */ + g_object_class_install_property (object_class, + PROP_COLUMN_SPACING, + g_param_spec_uint ("column-spacing", + P_("Horizontal spacing"), + P_("The amount of horizontal space between two children"), + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE)); + + /** + * GtkFlowBox::child-activated: + * @box: the #GtkFlowBox on which the signal is emitted + * @child: the child that is activated + * + * The ::child-activated signal is emitted when a child has been + * activated by the user. + */ + signals[CHILD_ACTIVATED] = g_signal_new ("child-activated", + GTK_TYPE_FLOW_BOX, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkFlowBoxClass, child_activated), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_WIDGET); + + /** + * GtkFlowBox::selected-children-changed: + * @box: the #GtkFlowBox on wich the signal is emitted + * + * The ::selected-children-changed signal is emitted when the + * set of selected children changes. + * + * Use gtk_flow_box_selected_foreach() or + * gtk_flow_box_get_selected_children() to obtain the + * selected children. + */ + signals[SELECTED_CHILDREN_CHANGED] = g_signal_new ("selected-children-changed", + GTK_TYPE_FLOW_BOX, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkFlowBoxClass, selected_children_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GtkFlowBox::activate-cursor-child: + * @box: the #GtkFlowBox on which the signal is emitted + * + * The ::activate-cursor-child signal is a + * keybinding signal + * which gets emitted when the user activates the @box. + */ + signals[ACTIVATE_CURSOR_CHILD] = g_signal_new ("activate-cursor-child", + GTK_TYPE_FLOW_BOX, + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkFlowBoxClass, activate_cursor_child), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GtkFlowBox::toggle-cursor-child: + * @box: the #GtkFlowBox on which the signal is emitted + * + * The ::toggle-cursor-child signal is a + * keybinding signal + * which toggles the selection of the child that has the focus. + * + * The default binding for this signal is Ctrl-Space. + */ + signals[TOGGLE_CURSOR_CHILD] = g_signal_new ("toggle-cursor-child", + GTK_TYPE_FLOW_BOX, + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkFlowBoxClass, toggle_cursor_child), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GtkFlowBox::move-cursor: + * @box: the #GtkFlowBox on which the signal is emitted + * @step: the granularity fo the move, as a #GtkMovementStep + * @count: the number of @step units to move + * + * The ::move-cursor signal is a + * keybinding signal + * which gets emitted when the user initiates a cursor movement. + * If the cursor is not visible in @text_view, this signal causes + * the viewport to be moved instead. + * + * Applications should not connect to it, but may emit it with + * g_signal_emit_by_name() if they need to control the cursor + * programmatically. + * + * The default bindings for this signal come in two variants, + * the variant with the Shift modifier extends the selection, + * the variant without the Shift modifer does not. + * There are too many key combinations to list them all here. + * + * Arrow keys move by individual children + * Home/End keys move to the ends of the box + * PageUp/PageDown keys move vertically by pages + * + */ + signals[MOVE_CURSOR] = g_signal_new ("move-cursor", + GTK_TYPE_FLOW_BOX, + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkFlowBoxClass, move_cursor), + NULL, NULL, + _gtk_marshal_VOID__ENUM_INT, + G_TYPE_NONE, 2, + GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT); + /** + * GtkFlowBox::select-all: + * @box: the #GtkFlowBox on which the signal is emitted + * + * The ::select-all signal is a + * keybinding signal + * which gets emitted to select all children of the box, if + * the selection mode permits it. + * + * The default bindings for this signal is Ctrl-a. + */ + signals[SELECT_ALL] = g_signal_new ("select-all", + GTK_TYPE_FLOW_BOX, + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkFlowBoxClass, select_all), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GtkFlowBox::unselect-all: + * @box: the #GtkFlowBox on which the signal is emitted + * + * The ::unselect-all signal is a + * keybinding signal + * which gets emitted to unselect all children of the box, if + * the selection mode permits it. + * + * The default bindings for this signal is Ctrl-Shift-a. + */ + signals[UNSELECT_ALL] = g_signal_new ("unselect-all", + GTK_TYPE_FLOW_BOX, + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkFlowBoxClass, unselect_all), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + widget_class->activate_signal = signals[ACTIVATE_CURSOR_CHILD]; + + binding_set = gtk_binding_set_by_class (class); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Home, 0, + GTK_MOVEMENT_BUFFER_ENDS, -1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Home, 0, + GTK_MOVEMENT_BUFFER_ENDS, -1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_End, 0, + GTK_MOVEMENT_BUFFER_ENDS, 1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_End, 0, + GTK_MOVEMENT_BUFFER_ENDS, 1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Up, 0, + GTK_MOVEMENT_DISPLAY_LINES, -1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Up, 0, + GTK_MOVEMENT_DISPLAY_LINES, -1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Down, 0, + GTK_MOVEMENT_DISPLAY_LINES, 1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Down, 0, + GTK_MOVEMENT_DISPLAY_LINES, 1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Page_Up, 0, + GTK_MOVEMENT_PAGES, -1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Up, 0, + GTK_MOVEMENT_PAGES, -1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Page_Down, 0, + GTK_MOVEMENT_PAGES, 1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Down, 0, + GTK_MOVEMENT_PAGES, 1); + + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Right, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, 1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Right, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, 1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Left, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, -1); + gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Left, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, -1); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK, + "toggle-cursor-child", 0, NULL); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK, + "toggle-cursor-child", 0, NULL); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK, + "select-all", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK | GDK_SHIFT_MASK, + "unselect-all", 0); + + gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_FLOW_BOX_ACCESSIBLE); +} + +static void +gtk_flow_box_init (GtkFlowBox *box) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + + gtk_widget_set_has_window (GTK_WIDGET (box), TRUE); + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (box), TRUE); + + priv->orientation = GTK_ORIENTATION_HORIZONTAL; + priv->selection_mode = GTK_SELECTION_SINGLE; + priv->max_children_per_line = DEFAULT_MAX_CHILDREN_PER_LINE; + priv->column_spacing = 0; + priv->row_spacing = 0; + priv->activate_on_single_click = TRUE; + + priv->children = g_sequence_new (NULL); +} + + /* Public API {{{2 */ + +/** + * gtk_flow_box_new: + * + * Creates a GtkFlowBox. + * + * Returns: a new #GtkFlowBox container + * + * Since: 3.12 + */ +GtkWidget * +gtk_flow_box_new (void) +{ + return (GtkWidget *)g_object_new (GTK_TYPE_FLOW_BOX, NULL); +} + +/** + * gtk_flow_box_insert: + * @box: a #GtkFlowBox + * @widget: the #GtkWidget to add + * @position: the position to insert @child in + * + * Inserts the @widget into @box at @position. + * + * If a sort function is set, the widget will actually be inserted + * at the calculated position and this function has the same effect + * as gtk_container_add(). + * + * If @position is -1, or larger than the total number of children + * in the @box, then the @widget will be appended to the end. + * + * Since: 3.12 + */ +void +gtk_flow_box_insert (GtkFlowBox *box, + GtkWidget *widget, + gint position) +{ + GtkFlowBoxPrivate *priv; + GtkFlowBoxChild *child; + GSequenceIter *iter; + + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + priv = BOX_PRIV (box); + + if (GTK_IS_FLOW_BOX_CHILD (widget)) + child = GTK_FLOW_BOX_CHILD (widget); + else + { + child = GTK_FLOW_BOX_CHILD (gtk_flow_box_child_new ()); + gtk_widget_show (GTK_WIDGET (child)); + gtk_container_add (GTK_CONTAINER (child), widget); + } + + if (priv->sort_func != NULL) + iter = g_sequence_insert_sorted (priv->children, child, + (GCompareDataFunc)gtk_flow_box_sort, box); + else if (position == 0) + iter = g_sequence_prepend (priv->children, child); + else if (position == -1) + iter = g_sequence_append (priv->children, child); + else + { + GSequenceIter *pos; + pos = g_sequence_get_iter_at_pos (priv->children, position); + iter = g_sequence_insert_before (pos, child); + } + + CHILD_PRIV (child)->iter = iter; + gtk_widget_set_parent (GTK_WIDGET (child), GTK_WIDGET (box)); + gtk_flow_box_apply_filter (box, child); +} + +/** + * gtk_flow_box_get_child_at_index: + * @box: a #GtkFlowBox + * @idx: the position of the child + * + * Gets the nth child in the @box. + * + * Returns: (transfer none): the child widget, which will + * always be a #GtkFlowBoxChild + * + * Since: 3.12 + */ +GtkFlowBoxChild * +gtk_flow_box_get_child_at_index (GtkFlowBox *box, + gint idx) +{ + GSequenceIter *iter; + + g_return_val_if_fail (GTK_IS_FLOW_BOX (box), NULL); + + iter = g_sequence_get_iter_at_pos (BOX_PRIV (box)->children, idx); + if (iter) + return g_sequence_get (iter); + + return NULL; +} + +/** + * gtk_flow_box_set_hadjustment: + * @box: a #GtkFlowBox + * @adjustment: an adjustment which should be adjusted + * when the focus is moved among the descendents of @container + * + * Hooks up an adjustment to focus handling in @box. The + * adjustment is also used for autoscrolling during + * rubberband selection. See gtk_scrolled_window_get_hadjustment() + * for a typical way of obtaining the adjustment, and + * gtk_flow_box_set_vadjustment()for setting the vertical + * adjustment. + * + * The adjustments have to be in pixel units and in the same + * coordinate system as the allocation for immediate children + * of the box. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_hadjustment (GtkFlowBox *box, + GtkAdjustment *adjustment) +{ + GtkFlowBoxPrivate *priv; + + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); + + priv = BOX_PRIV (box); + + g_object_ref (adjustment); + if (priv->hadjustment) + g_object_unref (priv->hadjustment); + priv->hadjustment = adjustment; + gtk_container_set_focus_hadjustment (GTK_CONTAINER (box), adjustment); +} + +/** + * gtk_flow_box_set_vadjustment: + * @box: a #GtkFlowBox + * @adjustment: an adjustment which should be adjusted + * when the focus is moved among the descendents of @container + * + * Hooks up an adjustment to focus handling in @box. The + * adjustment is also used for autoscrolling during + * rubberband selection. See gtk_scrolled_window_get_vadjustment() + * for a typical way of obtaining the adjustment, and + * gtk_flow_box_set_hadjustment()for setting the horizontal + * adjustment. + * + * The adjustments have to be in pixel units and in the same + * coordinate system as the allocation for immediate children + * of the box. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_vadjustment (GtkFlowBox *box, + GtkAdjustment *adjustment) +{ + GtkFlowBoxPrivate *priv; + + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); + + priv = BOX_PRIV (box); + + g_object_ref (adjustment); + if (priv->vadjustment) + g_object_unref (priv->vadjustment); + priv->vadjustment = adjustment; + gtk_container_set_focus_vadjustment (GTK_CONTAINER (box), adjustment); +} + +/* Setters and getters {{{2 */ + +/** + * gtk_flow_box_get_homogeneous: + * @box: a #GtkFlowBox + * + * Returns whether the box is homogeneous (all children are the + * same size). See gtk_box_set_homogeneous(). + * + * Return value: %TRUE if the box is homogeneous. + * + * Since: 3.12 + */ +gboolean +gtk_flow_box_get_homogeneous (GtkFlowBox *box) +{ + g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE); + + return BOX_PRIV (box)->homogeneous; +} + +/** + * gtk_flow_box_set_homogeneous: + * @box: a #GtkFlowBox + * @homogeneous: %TRUE to create equal allotments, + * %FALSE for variable allotments + * + * Sets the #GtkFlowBox:homogeneous property of @box, controlling + * whether or not all children of @box are given equal space + * in the box. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_homogeneous (GtkFlowBox *box, + gboolean homogeneous) +{ + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + homogeneous = homogeneous != FALSE; + + if (BOX_PRIV (box)->homogeneous != homogeneous) + { + BOX_PRIV (box)->homogeneous = homogeneous; + + g_object_notify (G_OBJECT (box), "homogeneous"); + gtk_widget_queue_resize (GTK_WIDGET (box)); + } +} + +/** + * gtk_flow_box_set_row_spacing: + * @box: a #GtkFlowBox + * @spacing: the spacing to use + * + * Sets the vertical space to add between children. + * See the #GtkFlowBox:row-spacing property. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_row_spacing (GtkFlowBox *box, + guint spacing) +{ + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + if (BOX_PRIV (box)->row_spacing != spacing) + { + BOX_PRIV (box)->row_spacing = spacing; + + gtk_widget_queue_resize (GTK_WIDGET (box)); + g_object_notify (G_OBJECT (box), "row-spacing"); + } +} + +/** + * gtk_flow_box_get_row_spacing: + * @box: a #GtkFlowBox + * + * Gets the vertical spacing. + * + * Returns: the vertical spacing + * + * Since: 3.12 + */ +guint +gtk_flow_box_get_row_spacing (GtkFlowBox *box) +{ + g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE); + + return BOX_PRIV (box)->row_spacing; +} + +/** + * gtk_flow_box_set_column_spacing: + * @box: a #GtkFlowBox + * @spacing: the spacing to use + * + * Sets the horizontal space to add between children. + * See the #GtkFlowBox:column-spacing property. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_column_spacing (GtkFlowBox *box, + guint spacing) +{ + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + if (BOX_PRIV (box)->column_spacing != spacing) + { + BOX_PRIV (box)->column_spacing = spacing; + + gtk_widget_queue_resize (GTK_WIDGET (box)); + g_object_notify (G_OBJECT (box), "column-spacing"); + } +} + +/** + * gtk_flow_box_get_column_spacing: + * @box: a #GtkFlowBox + * + * Gets the horizontal spacing. + * + * Returns: the horizontal spacing + * + * Since: 3.12 + */ +guint +gtk_flow_box_get_column_spacing (GtkFlowBox *box) +{ + g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE); + + return BOX_PRIV (box)->column_spacing; +} + +/** + * gtk_flow_box_set_min_children_per_line: + * @box: a #GtkFlowBox + * @n_children: the minimum number of children per line + * + * Sets the minimum number of children to line up + * in @box's orientation before flowing. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_min_children_per_line (GtkFlowBox *box, + guint n_children) +{ + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + if (BOX_PRIV (box)->min_children_per_line != n_children) + { + BOX_PRIV (box)->min_children_per_line = n_children; + + gtk_widget_queue_resize (GTK_WIDGET (box)); + g_object_notify (G_OBJECT (box), "min-children-per-line"); + } +} + +/** + * gtk_flow_box_get_min_children_per_line: + * @box: a #GtkFlowBox + * + * Gets the minimum number of children per line. + * + * Returns: the minimum number of children per line + * + * Since: 3.12 + */ +guint +gtk_flow_box_get_min_children_per_line (GtkFlowBox *box) +{ + g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE); + + return BOX_PRIV (box)->min_children_per_line; +} + +/** + * gtk_flow_box_set_max_children_per_line: + * @box: a #GtkFlowBox + * @n_children: the maximum number of children per line + * + * Sets the maximum number of children to request and + * allocate space for in @box's orientation. + * + * Setting the maximum number of children per line + * limits the overall natural size request to be no more + * than @n_children children long in the given orientation. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_max_children_per_line (GtkFlowBox *box, + guint n_children) +{ + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + if (BOX_PRIV (box)->max_children_per_line != n_children) + { + BOX_PRIV (box)->max_children_per_line = n_children; + + gtk_widget_queue_resize (GTK_WIDGET (box)); + g_object_notify (G_OBJECT (box), "max-children-per-line"); + } +} + +/** + * gtk_flow_box_get_max_children_per_line: + * @box: a #GtkFlowBox + * + * Gets the maximum number of children per line. + * + * Returns: the maximum number of children per line + * + * Since: 3.12 + */ +guint +gtk_flow_box_get_max_children_per_line (GtkFlowBox *box) +{ + g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE); + + return BOX_PRIV (box)->max_children_per_line; +} + +/** + * gtk_flow_box_set_activate_on_single_click: + * @box: a #GtkFlowBox + * @single: %TRUE to emit child-activated on a single click + * + * If @single is %TRUE, children will be activated when you click + * on them, otherwise you need to double-click. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_activate_on_single_click (GtkFlowBox *box, + gboolean single) +{ + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + single = single != FALSE; + + if (BOX_PRIV (box)->activate_on_single_click != single) + { + BOX_PRIV (box)->activate_on_single_click = single; + g_object_notify (G_OBJECT (box), "activate-on-single-click"); + } +} + +/** + * gtk_flow_box_get_activate_on_single_click: + * @box: a #GtkFlowBox + * + * Returns whether children activate on single clicks. + * + * Returns: %TRUE if children are activated on single click, + * %FALSE otherwise + * + * Since: 3.12 + */ +gboolean +gtk_flow_box_get_activate_on_single_click (GtkFlowBox *box) +{ + g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE); + + return BOX_PRIV (box)->activate_on_single_click; +} + +/* Selection handling {{{2 */ + +/** + * gtk_flow_box_get_selected_children: + * @box: a #GtkFlowBox + * + * Creates a list of all selected children. + * + * Returns: (element-type GtkFlowBoxChild) (transfer container): + * A #GList containing the #GtkWidget for each selected child. + * Free with g_list_free() when done. + * + * Since: 3.12 + */ +GList * +gtk_flow_box_get_selected_children (GtkFlowBox *box) +{ + GtkFlowBoxChild *child; + GSequenceIter *iter; + GList *selected = NULL; + + g_return_val_if_fail (GTK_IS_FLOW_BOX (box), NULL); + + for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + child = g_sequence_get (iter); + if (CHILD_PRIV (child)->selected) + selected = g_list_prepend (selected, child); + } + + return g_list_reverse (selected); +} + +/** + * gtk_flow_box_select_child: + * @box: a #GtkFlowBox + * @child: a child of @box + * + * Selects a single child of @box, if the selection + * mode allows it. + * + * Since: 3.12 + */ +void +gtk_flow_box_select_child (GtkFlowBox *box, + GtkFlowBoxChild *child) +{ + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child)); + + gtk_flow_box_select_child_info (box, child); +} + +/** + * gtk_flow_box_unselect_child: + * @box: a #GtkFlowBox + * @child: a child of @box + * + * Unselects a single child of @box, if the selection + * mode allows it. + * + * Since: 3.12 + */ +void +gtk_flow_box_unselect_child (GtkFlowBox *box, + GtkFlowBoxChild *child) +{ + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child)); + + gtk_flow_box_unselect_child_info (box, child); +} + +/** + * gtk_flow_box_select_all: + * @box: a #GtkFlowBox + * + * Select all children of @box, if the selection + * mode allows it. + * + * Since: 3.12 + */ +void +gtk_flow_box_select_all (GtkFlowBox *box) +{ + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE) + return; + + if (g_sequence_get_length (BOX_PRIV (box)->children) > 0) + { + gtk_flow_box_select_all_between (box, NULL, NULL); + g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0); + } +} + +/** + * gtk_flow_box_unselect_all: + * @box: a #GtkFlowBox + * + * Unselect all children of @box, if the selection + * mode allows it. + * + * Since: 3.12 + */ +void +gtk_flow_box_unselect_all (GtkFlowBox *box) +{ + gboolean dirty = FALSE; + + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_BROWSE) + return; + + dirty = gtk_flow_box_unselect_all_internal (box); + + if (dirty) + g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0); +} + +/** + * GtkFlowBoxForeachFunc: + * @box: a #GtkFlowBox + * @child: a #GtkFlowBoxChild + * @user_data: (closure): user data + * + * A function used by gtk_flow_box_selected_foreach(). + * It will be called on every selected child of tht @box. + * + * Since: 3.12 + */ + +/** + * gtk_flow_box_selected_foreach: + * @box: a #GtkFlowBox + * @func: (scope call): the function to call for each selected child + * @data: user data to pass to the function + * + * Calls a function for each selected child. + * + * Note that the selection cannot be modified from within + * this function. + * + * Since: 3.12 + */ +void +gtk_flow_box_selected_foreach (GtkFlowBox *box, + GtkFlowBoxForeachFunc func, + gpointer data) +{ + GtkFlowBoxChild *child; + GSequenceIter *iter; + + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + child = g_sequence_get (iter); + if (CHILD_PRIV (child)->selected) + (*func) (box, child, data); + } +} + +/** + * gtk_flow_box_set_selection_mode: + * @box: a #GtkFlowBox + * @mode: the new selection mode + * + * Sets how selection works in @box. + * See #GtkSelectionMode for details. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_selection_mode (GtkFlowBox *box, + GtkSelectionMode mode) +{ + gboolean dirty = FALSE; + + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + if (mode == BOX_PRIV (box)->selection_mode) + return; + + if (mode == GTK_SELECTION_NONE || + BOX_PRIV (box)->selection_mode == GTK_SELECTION_MULTIPLE) + { + dirty = gtk_flow_box_unselect_all_internal (box); + BOX_PRIV (box)->selected_child = NULL; + } + + BOX_PRIV (box)->selection_mode = mode; + + g_object_notify (G_OBJECT (box), "selection-mode"); + + if (dirty) + g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0); +} + +/** + * gtk_flow_box_get_selection_mode: + * @box: a #GtkFlowBox + * + * Gets the selection mode of @box. + * + * Returns: the #GtkSelectionMode + * + * Since: 3.12 + */ +GtkSelectionMode +gtk_flow_box_get_selection_mode (GtkFlowBox *box) +{ + g_return_val_if_fail (GTK_IS_FLOW_BOX (box), GTK_SELECTION_SINGLE); + + return BOX_PRIV (box)->selection_mode; +} + +/* Filtering {{{2 */ + +/** + * GtkFlowBoxFilterFunc: + * @child: a #GtkFlowBoxChild that may be filtered + * @user_data: (closure): user data + * + * A function that will be called whenrever a child changes + * or is added. It lets you control if the child should be + * visible or not. + * + * Returns: %TRUE if the row should be visible, %FALSE otherwise + * + * Since: 3.12 + */ + +/** + * gtk_flow_box_set_filter_func: + * @box: a #GtkFlowBox + * @filter_func: (closure user_data) (allow-none): callback that + * lets you filter which children to show + * @user_data: user data passed to @filter_func + * @destroy: destroy notifier for @user_data + * + * By setting a filter function on the @box one can decide dynamically + * which of the children to show. For instance, to implement a search + * function that only shows the children matching the search terms. + * + * The @filter_func will be called for ach child after the call, and + * it will continue to be called each time a child changes (via + * gtk_flow_box_child_changed()) or when gtk_flow_box_invalidate_filter() + * is called. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_filter_func (GtkFlowBox *box, + GtkFlowBoxFilterFunc filter_func, + gpointer user_data, + GDestroyNotify destroy) +{ + GtkFlowBoxPrivate *priv; + + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + priv = BOX_PRIV (box); + + if (priv->filter_destroy != NULL) + priv->filter_destroy (priv->filter_data); + + priv->filter_func = filter_func; + priv->filter_data = user_data; + priv->filter_destroy = destroy; + + gtk_flow_box_invalidate_filter (box); +} + +/** + * gtk_flow_box_invalidate_filter: + * @box: a #GtkFlowBox + * + * Updates the filtering for all children. + * + * Call this function when the result of the filter + * function on the @box is changed due ot an external + * factor. For instance, this would be used if the + * filter function just looked for a specific search + * term, and the entry with the string has changed. + * + * Since: 3.12 + */ +void +gtk_flow_box_invalidate_filter (GtkFlowBox *box) +{ + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + if (BOX_PRIV (box)->filter_func != NULL) + { + GSequenceIter *iter; + + for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkFlowBoxChild *child; + + child = g_sequence_get (iter); + gtk_flow_box_apply_filter (box, child); + } + gtk_widget_queue_resize (GTK_WIDGET (box)); + } +} + +/* Sorting {{{2 */ + +/** + * GtkFlowBoxSortFunc: + * @child1: the first child + * @child2: the second child + * @user_data: (closure): user data + * + * A function to compare two children to determine which + * should come first. + * + * Returns: < 0 if @child1 should be before @child2, 0 if + * the are equal, and > 0 otherwise + * + * Since: 3.12 + */ + +/** + * gtk_flow_box_set_sort_func: + * @box: a #GtkFlowBox + * @sort_func: (closure user_data) (allow-none): the sort function + * @user_data: user data passed to @sort_func + * @destroy: destroy notifier for @user_data + * + * By setting a sort function on the @box, one can dynamically + * reorder the children of the box, based on the contents of + * the children. + * + * The @sort_func will be called for each child after the call, + * and will continue to be called each time a child changes (via + * gtk_flow_box_child_changed()) and when gtk_flow_box_invalidate_sort() + * is called. + * + * Since: 3.12 + */ +void +gtk_flow_box_set_sort_func (GtkFlowBox *box, + GtkFlowBoxSortFunc sort_func, + gpointer user_data, + GDestroyNotify destroy) +{ + GtkFlowBoxPrivate *priv; + + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + priv = BOX_PRIV (box); + + if (priv->sort_destroy != NULL) + priv->sort_destroy (priv->sort_data); + + priv->sort_func = sort_func; + priv->sort_data = user_data; + priv->sort_destroy = destroy; + + gtk_flow_box_invalidate_sort (box); +} + +static gint +gtk_flow_box_sort (GtkFlowBoxChild *a, + GtkFlowBoxChild *b, + GtkFlowBox *box) +{ + GtkFlowBoxPrivate *priv = BOX_PRIV (box); + + return priv->sort_func (a, b, priv->sort_data); +} + +/** + * gtk_flow_box_invalidate_sort: + * @box: a #GtkFlowBox + * + * Updates the sorting for all children. + * + * Call this when the result of the sort function on + * @box is changed due to an external factor. + * + * Since: 3.12 + */ +void +gtk_flow_box_invalidate_sort (GtkFlowBox *box) +{ + GtkFlowBoxPrivate *priv; + + g_return_if_fail (GTK_IS_FLOW_BOX (box)); + + priv = BOX_PRIV (box); + + if (priv->sort_func != NULL) + { + g_sequence_sort (priv->children, + (GCompareDataFunc)gtk_flow_box_sort, box); + gtk_widget_queue_resize (GTK_WIDGET (box)); + } +} + +/* vim:set foldmethod=marker: */ diff --git a/gtk/gtkflowbox.h b/gtk/gtkflowbox.h new file mode 100644 index 0000000000..7989706e03 --- /dev/null +++ b/gtk/gtkflowbox.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2010 Openismus GmbH + * Copyright (C) 2013 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Tristan Van Berkom + * Matthias Clasen + * William Jon McCann + */ + +#ifndef __GTK_FLOW_BOX_H__ +#define __GTK_FLOW_BOX_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + + +#define GTK_TYPE_FLOW_BOX (gtk_flow_box_get_type ()) +#define GTK_FLOW_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FLOW_BOX, GtkFlowBox)) +#define GTK_FLOW_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FLOW_BOX, GtkFlowBoxClass)) +#define GTK_IS_FLOW_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FLOW_BOX)) +#define GTK_IS_FLOW_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FLOW_BOX)) +#define GTK_FLOW_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FLOW_BOX, GtkFlowBoxClass)) + +typedef struct _GtkFlowBox GtkFlowBox; +typedef struct _GtkFlowBoxClass GtkFlowBoxClass; + +typedef struct _GtkFlowBoxChild GtkFlowBoxChild; +typedef struct _GtkFlowBoxChildClass GtkFlowBoxChildClass; + +struct _GtkFlowBox +{ + GtkContainer container; +}; + +struct _GtkFlowBoxClass +{ + GtkContainerClass parent_class; + + void (*child_activated) (GtkFlowBox *box, + GtkFlowBoxChild *child); + void (*selected_children_changed) (GtkFlowBox *box); + void (*activate_cursor_child) (GtkFlowBox *box); + void (*toggle_cursor_child) (GtkFlowBox *box); + void (*move_cursor) (GtkFlowBox *box, + GtkMovementStep step, + gint count); + void (*select_all) (GtkFlowBox *box); + void (*unselect_all) (GtkFlowBox *box); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); + void (*_gtk_reserved5) (void); + void (*_gtk_reserved6) (void); +}; + +#define GTK_TYPE_FLOW_BOX_CHILD (gtk_flow_box_child_get_type ()) +#define GTK_FLOW_BOX_CHILD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FLOW_BOX_CHILD, GtkFlowBoxChild)) +#define GTK_FLOW_BOX_CHILD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FLOW_BOX_CHILD, GtkFlowBoxChildClass)) +#define GTK_IS_FLOW_BOX_CHILD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FLOW_BOX_CHILD)) +#define GTK_IS_FLOW_BOX_CHILD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FLOW_BOX_CHILD)) +#define GTK_FLOW_BOX_CHILD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EG_TYPE_FLOW_BOX_CHILD, GtkFlowBoxChildClass)) + +struct _GtkFlowBoxChild +{ + GtkBin parent_instance; +}; + +struct _GtkFlowBoxChildClass +{ + GtkBinClass parent_class; + + void (* activate) (GtkFlowBoxChild *child); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); +}; + +GDK_AVAILABLE_IN_3_12 +GType gtk_flow_box_child_get_type (void) G_GNUC_CONST; +GDK_AVAILABLE_IN_3_12 +GtkWidget* gtk_flow_box_child_new (void); +GDK_AVAILABLE_IN_3_12 +gint gtk_flow_box_child_get_index (GtkFlowBoxChild *child); +GDK_AVAILABLE_IN_3_12 +gboolean gtk_flow_box_child_is_selected (GtkFlowBoxChild *child); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_child_changed (GtkFlowBoxChild *child); + + +GDK_AVAILABLE_IN_3_12 +GType gtk_flow_box_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_3_12 +GtkWidget *gtk_flow_box_new (void); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_homogeneous (GtkFlowBox *box, + gboolean homogeneous); +GDK_AVAILABLE_IN_3_12 +gboolean gtk_flow_box_get_homogeneous (GtkFlowBox *box); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_row_spacing (GtkFlowBox *box, + guint spacing); +GDK_AVAILABLE_IN_3_12 +guint gtk_flow_box_get_row_spacing (GtkFlowBox *box); + +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_column_spacing (GtkFlowBox *box, + guint spacing); +GDK_AVAILABLE_IN_3_12 +guint gtk_flow_box_get_column_spacing (GtkFlowBox *box); + +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_min_children_per_line (GtkFlowBox *box, + guint n_children); +GDK_AVAILABLE_IN_3_12 +guint gtk_flow_box_get_min_children_per_line (GtkFlowBox *box); + +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_max_children_per_line (GtkFlowBox *box, + guint n_children); +GDK_AVAILABLE_IN_3_12 +guint gtk_flow_box_get_max_children_per_line (GtkFlowBox *box); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_activate_on_single_click (GtkFlowBox *box, + gboolean single); +GDK_AVAILABLE_IN_3_12 +gboolean gtk_flow_box_get_activate_on_single_click (GtkFlowBox *box); + +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_insert (GtkFlowBox *box, + GtkWidget *widget, + gint position); +GDK_AVAILABLE_IN_3_12 +GtkFlowBoxChild *gtk_flow_box_get_child_at_index (GtkFlowBox *box, + gint idx); + +typedef void (* GtkFlowBoxForeachFunc) (GtkFlowBox *box, + GtkFlowBoxChild *child, + gpointer user_data); + +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_selected_foreach (GtkFlowBox *box, + GtkFlowBoxForeachFunc func, + gpointer data); +GDK_AVAILABLE_IN_3_12 +GList *gtk_flow_box_get_selected_children (GtkFlowBox *box); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_select_child (GtkFlowBox *box, + GtkFlowBoxChild *child); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_unselect_child (GtkFlowBox *box, + GtkFlowBoxChild *child); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_select_all (GtkFlowBox *box); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_unselect_all (GtkFlowBox *box); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_selection_mode (GtkFlowBox *box, + GtkSelectionMode mode); +GDK_AVAILABLE_IN_3_12 +GtkSelectionMode gtk_flow_box_get_selection_mode (GtkFlowBox *box); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_hadjustment (GtkFlowBox *box, + GtkAdjustment *adjustment); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_vadjustment (GtkFlowBox *box, + GtkAdjustment *adjustment); + +typedef gboolean (*GtkFlowBoxFilterFunc) (GtkFlowBoxChild *child, + gpointer user_data); + +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_filter_func (GtkFlowBox *box, + GtkFlowBoxFilterFunc filter_func, + gpointer user_data, + GDestroyNotify destroy); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_invalidate_filter (GtkFlowBox *box); + +typedef gint (*GtkFlowBoxSortFunc) (GtkFlowBoxChild *child1, + GtkFlowBoxChild *child2, + gpointer user_data); + +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_set_sort_func (GtkFlowBox *box, + GtkFlowBoxSortFunc sort_func, + gpointer user_data, + GDestroyNotify destroy); +GDK_AVAILABLE_IN_3_12 +void gtk_flow_box_invalidate_sort (GtkFlowBox *box); + +G_END_DECLS + + +#endif /* __GTK_FLOW_BOX_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 5478b9f0be..e513f40e7f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -54,6 +54,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \ testentryicons \ testfilechooser \ testfilechooserbutton \ + testflowbox \ testfontselection \ testfontselectiondialog \ testfontchooser \ @@ -190,6 +191,7 @@ testentryicons_DEPENDENCIES = $(TEST_DEPS) testerrors_DEPENDENCIES = $(TEST_DEPS) testfilechooser_DEPENDENCIES = $(TEST_DEPS) testfilechooserbutton_DEPENDENCIES = $(TEST_DEPS) +testflowbox_DEPENDENCIES = $(TEST_DEPS) testfontselection_DEPENDENCIES = $(TEST_DEPS) testfontselectiondialog_DEPENDENCIES = $(TEST_DEPS) testfontchooser_DEPENDENCIES = $(TEST_DEPS) @@ -310,6 +312,9 @@ testfilechooserbutton_SOURCES = \ prop-editor.c \ testfilechooserbutton.c +testflowbox_SOURCES = \ + testflowbox.c + testfontselection_SOURCES = \ testfontselection.c diff --git a/tests/testflowbox.c b/tests/testflowbox.c new file mode 100644 index 0000000000..522231b714 --- /dev/null +++ b/tests/testflowbox.c @@ -0,0 +1,692 @@ +/* + * Copyright (C) 2010 Openismus GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +enum { + SIMPLE_ITEMS = 0, + FOCUS_ITEMS, + WRAPPY_ITEMS, + STOCK_ITEMS, + IMAGE_ITEMS +}; + +#define INITIAL_HALIGN GTK_ALIGN_FILL +#define INITIAL_VALIGN GTK_ALIGN_START +#define INITIAL_MINIMUM_LENGTH 3 +#define INITIAL_MAXIMUM_LENGTH 6 +#define INITIAL_CSPACING 2 +#define INITIAL_RSPACING 2 +#define N_ITEMS 1000 + +static GtkFlowBox *the_flowbox = NULL; +static gint items_type = SIMPLE_ITEMS; +static GtkOrientation text_orientation = GTK_ORIENTATION_HORIZONTAL; + +static void +populate_flowbox_simple (GtkFlowBox *flowbox) +{ + GtkWidget *widget, *frame; + gint i; + + for (i = 0; i < N_ITEMS; i++) + { + gchar *text = g_strdup_printf ("Item %02d", i); + + widget = gtk_label_new (text); + frame = gtk_frame_new (NULL); + gtk_widget_show (widget); + gtk_widget_show (frame); + + gtk_container_add (GTK_CONTAINER (frame), widget); + + if (text_orientation == GTK_ORIENTATION_VERTICAL) + gtk_label_set_angle (GTK_LABEL (widget), 90); + g_object_set_data_full (G_OBJECT (frame), "id", (gpointer)g_strdup (text), g_free); + gtk_container_add (GTK_CONTAINER (flowbox), frame); + + g_free (text); + } +} + +static void +populate_flowbox_focus (GtkFlowBox *flowbox) +{ + GtkWidget *widget, *frame, *box; + gint i; + gboolean sensitive; + + for (i = 0; i < 200; i++) + { + sensitive = TRUE; + frame = gtk_frame_new (NULL); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_add (GTK_CONTAINER (frame), box); + + widget = gtk_label_new ("Label"); + gtk_container_add (GTK_CONTAINER (box), widget); + + switch (i % 4) + { + case 0: + widget = gtk_entry_new (); + break; + case 1: + widget = gtk_button_new_with_label ("Button"); + break; + case 2: + widget = gtk_label_new ("bla"); + break; + case 3: + widget = gtk_label_new ("bla"); + sensitive = FALSE; + break; + } + + gtk_container_add (GTK_CONTAINER (box), widget); + + if (i % 5 == 0) + gtk_container_add (GTK_CONTAINER (box), gtk_switch_new ()); + + gtk_widget_show_all (frame); + + gtk_container_add (GTK_CONTAINER (flowbox), frame); + if (!sensitive) + gtk_widget_set_sensitive (gtk_widget_get_parent (frame), FALSE); + } +} + +static void +populate_flowbox_wrappy (GtkFlowBox *flowbox) +{ + GtkWidget *widget, *frame; + gint i; + + const gchar *strings[] = { + "These are", "some wrappy label", "texts", "of various", "lengths.", + "They should always be", "shown", "consecutively. Except it's", + "hard to say", "where exactly the", "label", "will wrap", "and where exactly", + "the actual", "container", "will wrap.", "This label is really really really long !", + "Let's add some more", "labels to the", + "mix. Just to", "make sure we", "got something to work", "with here." + }; + + for (i = 0; i < G_N_ELEMENTS (strings); i++) + { + widget = gtk_label_new (strings[i]); + frame = gtk_frame_new (NULL); + gtk_widget_show (widget); + gtk_widget_show (frame); + + if (text_orientation == GTK_ORIENTATION_VERTICAL) + gtk_label_set_angle (GTK_LABEL (widget), 90); + + gtk_container_add (GTK_CONTAINER (frame), widget); + + gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE); + gtk_label_set_line_wrap_mode (GTK_LABEL (widget), PANGO_WRAP_WORD); + gtk_label_set_width_chars (GTK_LABEL (widget), 10); + g_object_set_data_full (G_OBJECT (frame), "id", (gpointer)g_strdup (strings[i]), g_free); + + gtk_container_add (GTK_CONTAINER (flowbox), frame); + } +} + +static void +populate_flowbox_stock (GtkFlowBox *flowbox) +{ + GtkWidget *widget; + static GSList *stock_ids = NULL; + GSList *l; + gint i; + + if (!stock_ids) + stock_ids = gtk_stock_list_ids (); + + for (i = 0, l = stock_ids; i < 30 && l != NULL; i++, l = l->next) + { + gchar *stock_id = l->data; + gchar *text = g_strdup_printf ("Item %02d", i); + + widget = gtk_button_new_from_stock (stock_id); + gtk_widget_show (widget); + + g_object_set_data_full (G_OBJECT (widget), "id", (gpointer)g_strdup (text), g_free); + gtk_container_add (GTK_CONTAINER (flowbox), widget); + } +} + +static void +populate_flowbox_images (GtkFlowBox *flowbox) +{ + GtkWidget *widget, *image, *label; + gint i; + + for (i = 0; i < N_ITEMS; i++) + { + gchar *text = g_strdup_printf ("Item %02d", i); + + widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_set_hexpand (widget, TRUE); + + image = gtk_image_new_from_icon_name ("face-wink", GTK_ICON_SIZE_DIALOG); + gtk_widget_set_hexpand (image, TRUE); + gtk_image_set_pixel_size (GTK_IMAGE (image), 256); + + label = gtk_label_new (text); + + gtk_container_add (GTK_CONTAINER (widget), image); + gtk_container_add (GTK_CONTAINER (widget), label); + gtk_widget_show_all (widget); + + if (text_orientation == GTK_ORIENTATION_VERTICAL) + gtk_label_set_angle (GTK_LABEL (widget), 90); + + g_object_set_data_full (G_OBJECT (widget), "id", (gpointer)g_strdup (text), g_free); + gtk_container_add (GTK_CONTAINER (flowbox), widget); + + g_free (text); + } +} + +static void +populate_items (GtkFlowBox *flowbox) +{ + GList *children, *l; + + /* Remove all children first */ + children = gtk_container_get_children (GTK_CONTAINER (flowbox)); + for (l = children; l; l = l->next) + { + GtkWidget *child = l->data; + + gtk_container_remove (GTK_CONTAINER (flowbox), child); + } + g_list_free (children); + + if (items_type == SIMPLE_ITEMS) + populate_flowbox_simple (flowbox); + else if (items_type == FOCUS_ITEMS) + populate_flowbox_focus (flowbox); + else if (items_type == WRAPPY_ITEMS) + populate_flowbox_wrappy (flowbox); + else if (items_type == STOCK_ITEMS) + populate_flowbox_stock (flowbox); + else if (items_type == IMAGE_ITEMS) + populate_flowbox_images (flowbox); +} + +static void +horizontal_alignment_changed (GtkComboBox *box, + GtkFlowBox *flowbox) +{ + GtkAlign alignment = gtk_combo_box_get_active (box); + + gtk_widget_set_halign (GTK_WIDGET (flowbox), alignment); +} + +static void +vertical_alignment_changed (GtkComboBox *box, + GtkFlowBox *flowbox) +{ + GtkAlign alignment = gtk_combo_box_get_active (box); + + gtk_widget_set_valign (GTK_WIDGET (flowbox), alignment); +} + +static void +orientation_changed (GtkComboBox *box, + GtkFlowBox *flowbox) +{ + GtkOrientation orientation = gtk_combo_box_get_active (box); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (flowbox), orientation); +} + +static void +selection_mode_changed (GtkComboBox *box, + GtkFlowBox *flowbox) +{ + GtkSelectionMode mode = gtk_combo_box_get_active (box); + + gtk_flow_box_set_selection_mode (flowbox, mode); +} + +static void +line_length_changed (GtkSpinButton *spin, + GtkFlowBox *flowbox) +{ + gint length = gtk_spin_button_get_value_as_int (spin); + + gtk_flow_box_set_min_children_per_line (flowbox, length); +} + +static void +max_line_length_changed (GtkSpinButton *spin, + GtkFlowBox *flowbox) +{ + gint length = gtk_spin_button_get_value_as_int (spin); + + gtk_flow_box_set_max_children_per_line (flowbox, length); +} + +static void +spacing_changed (GtkSpinButton *button, + gpointer data) +{ + GtkOrientation orientation = GPOINTER_TO_INT (data); + gint state = gtk_spin_button_get_value_as_int (button); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_flow_box_set_column_spacing (the_flowbox, state); + else + gtk_flow_box_set_row_spacing (the_flowbox, state); +} + +static void +items_changed (GtkComboBox *box, + GtkFlowBox *flowbox) +{ + items_type = gtk_combo_box_get_active (box); + + populate_items (flowbox); +} + +static void +text_orientation_changed (GtkComboBox *box, + GtkFlowBox *flowbox) +{ + text_orientation = gtk_combo_box_get_active (box); + + populate_items (flowbox); +} + +static void +homogeneous_toggled (GtkToggleButton *button, + GtkFlowBox *flowbox) +{ + gboolean state = gtk_toggle_button_get_active (button); + + gtk_flow_box_set_homogeneous (flowbox, state); +} + +static void +on_child_activated (GtkFlowBox *self, + GtkWidget *child) +{ + const char *id; + id = g_object_get_data (G_OBJECT (gtk_bin_get_child (GTK_BIN (child))), "id"); + g_message ("Child activated %p: %s", child, id); +} + +static void +selection_foreach (GtkFlowBox *self, + GtkFlowBoxChild *child_info, + gpointer data) +{ + const char *id; + GtkWidget *child; + + child = gtk_bin_get_child (GTK_BIN (child_info)); + id = g_object_get_data (G_OBJECT (child), "id"); + g_message ("Child selected %p: %s", child, id); +} + +static void +on_selected_children_changed (GtkFlowBox *self) +{ + g_message ("Selection changed"); + //gtk_flow_box_selected_foreach (self, selection_foreach, NULL); +} + +static gboolean +filter_func (GtkFlowBoxChild *child, gpointer user_data) +{ + gint index; + + index = gtk_flow_box_child_get_index (child); + + return (index % 3) == 0; +} + +static void +filter_toggled (GtkToggleButton *button, + GtkFlowBox *flowbox) +{ + gboolean state = gtk_toggle_button_get_active (button); + + if (state) + gtk_flow_box_set_filter_func (flowbox, filter_func, NULL, NULL); + else + gtk_flow_box_set_filter_func (flowbox, NULL, NULL, NULL); +} + +static gint +sort_func (GtkFlowBoxChild *a, + GtkFlowBoxChild *b, + gpointer data) +{ + gchar *ida, *idb; + + ida = (gchar *)g_object_get_data (G_OBJECT (gtk_bin_get_child (GTK_BIN (a))), "id"); + idb = (gchar *)g_object_get_data (G_OBJECT (gtk_bin_get_child (GTK_BIN (b))), "id"); + return g_strcmp0 (ida, idb); +} + +static void +sort_toggled (GtkToggleButton *button, + GtkFlowBox *flowbox) +{ + gboolean state = gtk_toggle_button_get_active (button); + + if (state) + gtk_flow_box_set_sort_func (flowbox, sort_func, NULL, NULL); + else + gtk_flow_box_set_sort_func (flowbox, NULL, NULL, NULL); +} + +static GtkWidget * +create_window (void) +{ + GtkWidget *window, *hbox, *vbox, *flowbox_cntl, *items_cntl; + GtkWidget *flowbox, *widget, *expander, *swindow; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + + gtk_container_set_border_width (GTK_CONTAINER (window), 8); + + gtk_widget_show (vbox); + gtk_widget_show (hbox); + gtk_container_add (GTK_CONTAINER (window), hbox); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + + swindow = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + gtk_widget_show (swindow); + gtk_box_pack_start (GTK_BOX (hbox), swindow, TRUE, TRUE, 0); + + flowbox = gtk_flow_box_new (); + gtk_widget_set_halign (flowbox, GTK_ALIGN_END); + the_flowbox = (GtkFlowBox *)flowbox; + gtk_widget_set_halign (flowbox, INITIAL_HALIGN); + gtk_widget_set_valign (flowbox, INITIAL_VALIGN); + gtk_flow_box_set_column_spacing (GTK_FLOW_BOX (flowbox), INITIAL_CSPACING); + gtk_flow_box_set_row_spacing (GTK_FLOW_BOX (flowbox), INITIAL_RSPACING); + gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (flowbox), INITIAL_MINIMUM_LENGTH); + gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (flowbox), INITIAL_MAXIMUM_LENGTH); + gtk_widget_show (flowbox); + gtk_container_add (GTK_CONTAINER (swindow), flowbox); + + gtk_flow_box_set_hadjustment (GTK_FLOW_BOX (flowbox), + gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (swindow))); + gtk_flow_box_set_vadjustment (GTK_FLOW_BOX (flowbox), + gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (swindow))); + + g_signal_connect (flowbox, "child-activated", G_CALLBACK (on_child_activated), NULL); + g_signal_connect (flowbox, "selected-children-changed", G_CALLBACK (on_selected_children_changed), NULL); + + /* Add Flowbox test control frame */ + expander = gtk_expander_new ("Flow Box controls"); + gtk_expander_set_expanded (GTK_EXPANDER (expander), TRUE); + flowbox_cntl = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_widget_show (flowbox_cntl); + gtk_widget_show (expander); + gtk_container_add (GTK_CONTAINER (expander), flowbox_cntl); + gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0); + + widget = gtk_check_button_new_with_label ("Homogeneous"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set whether the items should be displayed at the same size"); + gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "toggled", + G_CALLBACK (homogeneous_toggled), flowbox); + + widget = gtk_check_button_new_with_label ("Activate on single click"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE); + g_object_bind_property (widget, "active", + flowbox, "activate-on-single-click", + G_BINDING_SYNC_CREATE); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0); + + /* Add alignment controls */ + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Fill"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Start"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "End"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Center"); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), INITIAL_HALIGN); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set the horizontal alignment policy"); + gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (horizontal_alignment_changed), flowbox); + + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Fill"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Start"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "End"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Center"); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), INITIAL_VALIGN); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set the vertical alignment policy"); + gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (vertical_alignment_changed), flowbox); + + /* Add Orientation control */ + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Horizontal"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Vertical"); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set the flowbox orientation"); + gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (orientation_changed), flowbox); + + /* Add selection mode control */ + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "None"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Single"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Browse"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Multiple"); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 1); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set the selection mode"); + gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (selection_mode_changed), flowbox); + + /* Add minimum line length in items control */ + widget = gtk_spin_button_new_with_range (1, 10, 1); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), INITIAL_MINIMUM_LENGTH); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set the minimum amount of items per line before wrapping"); + gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (line_length_changed), flowbox); + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (line_length_changed), flowbox); + + /* Add natural line length in items control */ + widget = gtk_spin_button_new_with_range (1, 10, 1); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), INITIAL_MAXIMUM_LENGTH); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set the natural amount of items per line "); + gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (max_line_length_changed), flowbox); + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (max_line_length_changed), flowbox); + + /* Add horizontal/vertical spacing controls */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_widget_show (hbox); + + widget = gtk_label_new ("H Spacing"); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0); + + widget = gtk_spin_button_new_with_range (0, 30, 1); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), INITIAL_CSPACING); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set the horizontal spacing between children"); + gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (spacing_changed), GINT_TO_POINTER (GTK_ORIENTATION_HORIZONTAL)); + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (spacing_changed), GINT_TO_POINTER (GTK_ORIENTATION_HORIZONTAL)); + + gtk_box_pack_start (GTK_BOX (flowbox_cntl), hbox, FALSE, FALSE, 0); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_widget_show (hbox); + + widget = gtk_label_new ("V Spacing"); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0); + + widget = gtk_spin_button_new_with_range (0, 30, 1); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), INITIAL_RSPACING); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set the vertical spacing between children"); + gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (spacing_changed), GINT_TO_POINTER (GTK_ORIENTATION_VERTICAL)); + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (spacing_changed), GINT_TO_POINTER (GTK_ORIENTATION_VERTICAL)); + + gtk_box_pack_start (GTK_BOX (flowbox_cntl), hbox, FALSE, FALSE, 0); + + /* filtering and sorting */ + + widget = gtk_check_button_new_with_label ("Filter"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set whether some items should be filtered out"); + gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "toggled", + G_CALLBACK (filter_toggled), flowbox); + + widget = gtk_check_button_new_with_label ("Sort"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set whether items should be sorted"); + gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "toggled", + G_CALLBACK (sort_toggled), flowbox); + + + /* Add test items control frame */ + expander = gtk_expander_new ("Test item controls"); + gtk_expander_set_expanded (GTK_EXPANDER (expander), TRUE); + items_cntl = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_widget_show (items_cntl); + gtk_widget_show (expander); + gtk_container_add (GTK_CONTAINER (expander), items_cntl); + gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0); + + /* Add Items control */ + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Simple"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Focus"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Wrappy"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Stock"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Images"); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set the item set to use"); + gtk_box_pack_start (GTK_BOX (items_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (items_changed), flowbox); + + + /* Add Text Orientation control */ + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Horizontal"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Vertical"); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0); + gtk_widget_show (widget); + + gtk_widget_set_tooltip_text (widget, "Set the item's text orientation (cant be done for stock buttons)"); + gtk_box_pack_start (GTK_BOX (items_cntl), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (text_orientation_changed), flowbox); + + populate_items (GTK_FLOW_BOX (flowbox)); + + /* This line was added only for the convenience of reproducing + * a height-for-width inside GtkScrolledWindow bug (bug 629778). + * -Tristan + */ + gtk_window_set_default_size (GTK_WINDOW (window), 390, -1); + + return window; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + + gtk_init (&argc, &argv); + + window = create_window (); + + g_signal_connect (window, "delete-event", + G_CALLBACK (gtk_main_quit), window); + + gtk_widget_show (window); + + gtk_main (); + + return 0; +}