columns.c 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277
  1. /*
  2. * columns.c - implementation of the `Columns' GTK layout container.
  3. */
  4. #include <assert.h>
  5. #include <gtk/gtk.h>
  6. #include "defs.h"
  7. #include "gtkcompat.h"
  8. #include "columns.h"
  9. #if GTK_CHECK_VERSION(2,0,0)
  10. /* The "focus" method lives in GtkWidget from GTK 2 onwards, but it
  11. * was in GtkContainer in GTK 1 */
  12. #define FOCUS_METHOD_SUPERCLASS GtkWidget
  13. #define FOCUS_METHOD_LOCATION widget_class /* used in columns_init */
  14. #define CHILD_FOCUS(cont, dir) gtk_widget_child_focus(GTK_WIDGET(cont), dir)
  15. #else
  16. #define FOCUS_METHOD_SUPERCLASS GtkContainer
  17. #define FOCUS_METHOD_LOCATION container_class
  18. #define CHILD_FOCUS(cont, dir) gtk_container_focus(GTK_CONTAINER(cont), dir)
  19. #endif
  20. static void columns_init(Columns *cols);
  21. static void columns_class_init(ColumnsClass *klass);
  22. #if !GTK_CHECK_VERSION(2,0,0)
  23. static void columns_finalize(GtkObject *object);
  24. #else
  25. static void columns_finalize(GObject *object);
  26. #endif
  27. static void columns_map(GtkWidget *widget);
  28. static void columns_unmap(GtkWidget *widget);
  29. #if !GTK_CHECK_VERSION(2,0,0)
  30. static void columns_draw(GtkWidget *widget, GdkRectangle *area);
  31. static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
  32. #endif
  33. static void columns_base_add(GtkContainer *container, GtkWidget *widget);
  34. static void columns_remove(GtkContainer *container, GtkWidget *widget);
  35. static void columns_forall(GtkContainer *container, gboolean include_internals,
  36. GtkCallback callback, gpointer callback_data);
  37. static gint columns_focus(FOCUS_METHOD_SUPERCLASS *container,
  38. GtkDirectionType dir);
  39. static GType columns_child_type(GtkContainer *container);
  40. #if GTK_CHECK_VERSION(3,0,0)
  41. static void columns_get_preferred_width(GtkWidget *widget,
  42. gint *min, gint *nat);
  43. static void columns_get_preferred_height(GtkWidget *widget,
  44. gint *min, gint *nat);
  45. static void columns_get_preferred_width_for_height(GtkWidget *widget,
  46. gint height,
  47. gint *min, gint *nat);
  48. static void columns_get_preferred_height_for_width(GtkWidget *widget,
  49. gint width,
  50. gint *min, gint *nat);
  51. #else
  52. static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
  53. #endif
  54. static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
  55. static GtkContainerClass *parent_class = NULL;
  56. #if !GTK_CHECK_VERSION(2,0,0)
  57. GType columns_get_type(void)
  58. {
  59. static GType columns_type = 0;
  60. if (!columns_type) {
  61. static const GtkTypeInfo columns_info = {
  62. "Columns",
  63. sizeof(Columns),
  64. sizeof(ColumnsClass),
  65. (GtkClassInitFunc) columns_class_init,
  66. (GtkObjectInitFunc) columns_init,
  67. /* reserved_1 */ NULL,
  68. /* reserved_2 */ NULL,
  69. (GtkClassInitFunc) NULL,
  70. };
  71. columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
  72. }
  73. return columns_type;
  74. }
  75. #else
  76. GType columns_get_type(void)
  77. {
  78. static GType columns_type = 0;
  79. if (!columns_type) {
  80. static const GTypeInfo columns_info = {
  81. sizeof(ColumnsClass),
  82. NULL,
  83. NULL,
  84. (GClassInitFunc) columns_class_init,
  85. NULL,
  86. NULL,
  87. sizeof(Columns),
  88. 0,
  89. (GInstanceInitFunc)columns_init,
  90. };
  91. columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
  92. &columns_info, 0);
  93. }
  94. return columns_type;
  95. }
  96. #endif
  97. static gint (*columns_inherited_focus)(FOCUS_METHOD_SUPERCLASS *container,
  98. GtkDirectionType direction);
  99. static void columns_class_init(ColumnsClass *klass)
  100. {
  101. #if !GTK_CHECK_VERSION(2,0,0)
  102. GtkObjectClass *object_class = (GtkObjectClass *)klass;
  103. GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
  104. GtkContainerClass *container_class = (GtkContainerClass *)klass;
  105. #else
  106. GObjectClass *object_class = G_OBJECT_CLASS(klass);
  107. GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
  108. GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
  109. #endif
  110. #if !GTK_CHECK_VERSION(2,0,0)
  111. parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
  112. #else
  113. parent_class = g_type_class_peek_parent(klass);
  114. #endif
  115. object_class->finalize = columns_finalize;
  116. widget_class->map = columns_map;
  117. widget_class->unmap = columns_unmap;
  118. #if !GTK_CHECK_VERSION(2,0,0)
  119. widget_class->draw = columns_draw;
  120. widget_class->expose_event = columns_expose;
  121. #endif
  122. #if GTK_CHECK_VERSION(3,0,0)
  123. widget_class->get_preferred_width = columns_get_preferred_width;
  124. widget_class->get_preferred_height = columns_get_preferred_height;
  125. widget_class->get_preferred_width_for_height =
  126. columns_get_preferred_width_for_height;
  127. widget_class->get_preferred_height_for_width =
  128. columns_get_preferred_height_for_width;
  129. #else
  130. widget_class->size_request = columns_size_request;
  131. #endif
  132. widget_class->size_allocate = columns_size_allocate;
  133. container_class->add = columns_base_add;
  134. container_class->remove = columns_remove;
  135. container_class->forall = columns_forall;
  136. container_class->child_type = columns_child_type;
  137. /* Save the previous value of this method. */
  138. if (!columns_inherited_focus)
  139. columns_inherited_focus = FOCUS_METHOD_LOCATION->focus;
  140. FOCUS_METHOD_LOCATION->focus = columns_focus;
  141. }
  142. static void columns_init(Columns *cols)
  143. {
  144. gtk_widget_set_has_window(GTK_WIDGET(cols), false);
  145. cols->children = NULL;
  146. cols->spacing = 0;
  147. }
  148. static void columns_child_free(gpointer vchild)
  149. {
  150. ColumnsChild *child = (ColumnsChild *)vchild;
  151. if (child->percentages)
  152. g_free(child->percentages);
  153. g_free(child);
  154. }
  155. static void columns_finalize(
  156. #if !GTK_CHECK_VERSION(2,0,0)
  157. GtkObject *object
  158. #else
  159. GObject *object
  160. #endif
  161. )
  162. {
  163. Columns *cols;
  164. g_return_if_fail(object != NULL);
  165. g_return_if_fail(IS_COLUMNS(object));
  166. cols = COLUMNS(object);
  167. #if !GTK_CHECK_VERSION(2,0,0)
  168. {
  169. GList *node;
  170. for (node = cols->children; node; node = node->next)
  171. if (node->data)
  172. columns_child_free(node->data);
  173. }
  174. g_list_free(cols->children);
  175. #else
  176. g_list_free_full(cols->children, columns_child_free);
  177. #endif
  178. cols->children = NULL;
  179. #if !GTK_CHECK_VERSION(2,0,0)
  180. GTK_OBJECT_CLASS(parent_class)->finalize(object);
  181. #else
  182. G_OBJECT_CLASS(parent_class)->finalize(object);
  183. #endif
  184. }
  185. /*
  186. * These appear to be thoroughly tedious functions; the only reason
  187. * we have to reimplement them at all is because we defined our own
  188. * format for our GList of children...
  189. */
  190. static void columns_map(GtkWidget *widget)
  191. {
  192. Columns *cols;
  193. ColumnsChild *child;
  194. GList *children;
  195. g_return_if_fail(widget != NULL);
  196. g_return_if_fail(IS_COLUMNS(widget));
  197. cols = COLUMNS(widget);
  198. gtk_widget_set_mapped(GTK_WIDGET(cols), true);
  199. for (children = cols->children;
  200. children && (child = children->data);
  201. children = children->next) {
  202. if (child->widget &&
  203. gtk_widget_get_visible(child->widget) &&
  204. !gtk_widget_get_mapped(child->widget))
  205. gtk_widget_map(child->widget);
  206. }
  207. }
  208. static void columns_unmap(GtkWidget *widget)
  209. {
  210. Columns *cols;
  211. ColumnsChild *child;
  212. GList *children;
  213. g_return_if_fail(widget != NULL);
  214. g_return_if_fail(IS_COLUMNS(widget));
  215. cols = COLUMNS(widget);
  216. gtk_widget_set_mapped(GTK_WIDGET(cols), false);
  217. for (children = cols->children;
  218. children && (child = children->data);
  219. children = children->next) {
  220. if (child->widget &&
  221. gtk_widget_get_visible(child->widget) &&
  222. gtk_widget_get_mapped(child->widget))
  223. gtk_widget_unmap(child->widget);
  224. }
  225. }
  226. #if !GTK_CHECK_VERSION(2,0,0)
  227. static void columns_draw(GtkWidget *widget, GdkRectangle *area)
  228. {
  229. Columns *cols;
  230. ColumnsChild *child;
  231. GList *children;
  232. GdkRectangle child_area;
  233. g_return_if_fail(widget != NULL);
  234. g_return_if_fail(IS_COLUMNS(widget));
  235. if (GTK_WIDGET_DRAWABLE(widget)) {
  236. cols = COLUMNS(widget);
  237. for (children = cols->children;
  238. children && (child = children->data);
  239. children = children->next) {
  240. if (child->widget &&
  241. GTK_WIDGET_DRAWABLE(child->widget) &&
  242. gtk_widget_intersect(child->widget, area, &child_area))
  243. gtk_widget_draw(child->widget, &child_area);
  244. }
  245. }
  246. }
  247. static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
  248. {
  249. Columns *cols;
  250. ColumnsChild *child;
  251. GList *children;
  252. GdkEventExpose child_event;
  253. g_return_val_if_fail(widget != NULL, false);
  254. g_return_val_if_fail(IS_COLUMNS(widget), false);
  255. g_return_val_if_fail(event != NULL, false);
  256. if (GTK_WIDGET_DRAWABLE(widget)) {
  257. cols = COLUMNS(widget);
  258. child_event = *event;
  259. for (children = cols->children;
  260. children && (child = children->data);
  261. children = children->next) {
  262. if (child->widget &&
  263. GTK_WIDGET_DRAWABLE(child->widget) &&
  264. GTK_WIDGET_NO_WINDOW(child->widget) &&
  265. gtk_widget_intersect(child->widget, &event->area,
  266. &child_event.area))
  267. gtk_widget_event(child->widget, (GdkEvent *)&child_event);
  268. }
  269. }
  270. return false;
  271. }
  272. #endif
  273. static void columns_base_add(GtkContainer *container, GtkWidget *widget)
  274. {
  275. Columns *cols;
  276. g_return_if_fail(container != NULL);
  277. g_return_if_fail(IS_COLUMNS(container));
  278. g_return_if_fail(widget != NULL);
  279. cols = COLUMNS(container);
  280. /*
  281. * Default is to add a new widget spanning all columns.
  282. */
  283. columns_add(cols, widget, 0, 0); /* 0 means ncols */
  284. }
  285. static void columns_remove(GtkContainer *container, GtkWidget *widget)
  286. {
  287. Columns *cols;
  288. ColumnsChild *child;
  289. GtkWidget *childw;
  290. GList *children;
  291. g_return_if_fail(container != NULL);
  292. g_return_if_fail(IS_COLUMNS(container));
  293. g_return_if_fail(widget != NULL);
  294. cols = COLUMNS(container);
  295. for (children = cols->children;
  296. children && (child = children->data);
  297. children = children->next) {
  298. if (child->widget != widget)
  299. continue;
  300. bool need_layout = false;
  301. if (gtk_widget_get_visible(widget))
  302. need_layout = true;
  303. gtk_widget_unparent(widget);
  304. cols->children = g_list_remove_link(cols->children, children);
  305. g_list_free(children);
  306. /* Unlink this widget from its valign list, and if anything
  307. * else on the list is still visible, ensure we recompute our
  308. * layout */
  309. for (ColumnsChild *ch = child->valign_next; ch != child;
  310. ch = ch->valign_next)
  311. if (gtk_widget_get_visible(ch->widget))
  312. need_layout = true;
  313. child->valign_next->valign_prev = child->valign_prev;
  314. child->valign_prev->valign_next = child->valign_next;
  315. if (cols->vexpand == child)
  316. cols->vexpand = NULL;
  317. g_free(child);
  318. if (need_layout)
  319. gtk_widget_queue_resize(GTK_WIDGET(container));
  320. break;
  321. }
  322. for (children = cols->taborder;
  323. children && (childw = children->data);
  324. children = children->next) {
  325. if (childw != widget)
  326. continue;
  327. cols->taborder = g_list_remove_link(cols->taborder, children);
  328. g_list_free(children);
  329. break;
  330. }
  331. }
  332. static void columns_forall(GtkContainer *container, gboolean include_internals,
  333. GtkCallback callback, gpointer callback_data)
  334. {
  335. Columns *cols;
  336. ColumnsChild *child;
  337. GList *children, *next;
  338. g_return_if_fail(container != NULL);
  339. g_return_if_fail(IS_COLUMNS(container));
  340. g_return_if_fail(callback != NULL);
  341. cols = COLUMNS(container);
  342. for (children = cols->children;
  343. children && (child = children->data);
  344. children = next) {
  345. /*
  346. * We can't wait until after the callback to assign
  347. * `children = children->next', because the callback might
  348. * be gtk_widget_destroy, which would remove the link
  349. * `children' from the list! So instead we must get our
  350. * hands on the value of the `next' pointer _before_ the
  351. * callback.
  352. */
  353. next = children->next;
  354. if (child->widget)
  355. callback(child->widget, callback_data);
  356. }
  357. }
  358. static GType columns_child_type(GtkContainer *container)
  359. {
  360. return GTK_TYPE_WIDGET;
  361. }
  362. GtkWidget *columns_new(gint spacing)
  363. {
  364. Columns *cols;
  365. #if !GTK_CHECK_VERSION(2,0,0)
  366. cols = gtk_type_new(columns_get_type());
  367. #else
  368. cols = g_object_new(TYPE_COLUMNS, NULL);
  369. #endif
  370. cols->spacing = spacing;
  371. return GTK_WIDGET(cols);
  372. }
  373. void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
  374. {
  375. ColumnsChild *childdata;
  376. gint i;
  377. g_return_if_fail(cols != NULL);
  378. g_return_if_fail(IS_COLUMNS(cols));
  379. g_return_if_fail(ncols > 0);
  380. g_return_if_fail(percentages != NULL);
  381. childdata = g_new(ColumnsChild, 1);
  382. childdata->widget = NULL;
  383. childdata->ncols = ncols;
  384. childdata->percentages = g_new(gint, ncols);
  385. childdata->force_left = false;
  386. for (i = 0; i < ncols; i++)
  387. childdata->percentages[i] = percentages[i];
  388. cols->children = g_list_append(cols->children, childdata);
  389. }
  390. void columns_add(Columns *cols, GtkWidget *child,
  391. gint colstart, gint colspan)
  392. {
  393. ColumnsChild *childdata;
  394. g_return_if_fail(cols != NULL);
  395. g_return_if_fail(IS_COLUMNS(cols));
  396. g_return_if_fail(child != NULL);
  397. g_return_if_fail(gtk_widget_get_parent(child) == NULL);
  398. childdata = g_new(ColumnsChild, 1);
  399. childdata->widget = child;
  400. childdata->colstart = colstart;
  401. childdata->colspan = colspan;
  402. childdata->force_left = false;
  403. childdata->valign_next = childdata;
  404. childdata->valign_prev = childdata;
  405. childdata->percentages = NULL;
  406. cols->children = g_list_append(cols->children, childdata);
  407. cols->taborder = g_list_append(cols->taborder, child);
  408. gtk_widget_set_parent(child, GTK_WIDGET(cols));
  409. if (gtk_widget_get_realized(GTK_WIDGET(cols)))
  410. gtk_widget_realize(child);
  411. if (gtk_widget_get_visible(GTK_WIDGET(cols)) &&
  412. gtk_widget_get_visible(child)) {
  413. if (gtk_widget_get_mapped(GTK_WIDGET(cols)))
  414. gtk_widget_map(child);
  415. gtk_widget_queue_resize(child);
  416. }
  417. }
  418. static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget)
  419. {
  420. GList *children;
  421. ColumnsChild *child;
  422. for (children = cols->children;
  423. children && (child = children->data);
  424. children = children->next) {
  425. if (child->widget == widget)
  426. return child;
  427. }
  428. return NULL;
  429. }
  430. void columns_force_left_align(Columns *cols, GtkWidget *widget)
  431. {
  432. ColumnsChild *child;
  433. g_return_if_fail(cols != NULL);
  434. g_return_if_fail(IS_COLUMNS(cols));
  435. g_return_if_fail(widget != NULL);
  436. child = columns_find_child(cols, widget);
  437. g_return_if_fail(child != NULL);
  438. child->force_left = true;
  439. if (gtk_widget_get_visible(widget))
  440. gtk_widget_queue_resize(GTK_WIDGET(cols));
  441. }
  442. void columns_align_next_to(Columns *cols, GtkWidget *cw1, GtkWidget *cw2)
  443. {
  444. ColumnsChild *child1, *child2;
  445. g_return_if_fail(cols != NULL);
  446. g_return_if_fail(IS_COLUMNS(cols));
  447. g_return_if_fail(cw1 != NULL);
  448. g_return_if_fail(cw2 != NULL);
  449. child1 = columns_find_child(cols, cw1);
  450. g_return_if_fail(child1 != NULL);
  451. child2 = columns_find_child(cols, cw2);
  452. g_return_if_fail(child2 != NULL);
  453. ColumnsChild *child1prev = child1->valign_prev;
  454. ColumnsChild *child2prev = child2->valign_prev;
  455. child1prev->valign_next = child2;
  456. child2->valign_prev = child1prev;
  457. child2prev->valign_next = child1;
  458. child1->valign_prev = child2prev;
  459. if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2))
  460. gtk_widget_queue_resize(GTK_WIDGET(cols));
  461. }
  462. void columns_taborder_last(Columns *cols, GtkWidget *widget)
  463. {
  464. GtkWidget *childw;
  465. GList *children;
  466. g_return_if_fail(cols != NULL);
  467. g_return_if_fail(IS_COLUMNS(cols));
  468. g_return_if_fail(widget != NULL);
  469. for (children = cols->taborder;
  470. children && (childw = children->data);
  471. children = children->next) {
  472. if (childw != widget)
  473. continue;
  474. cols->taborder = g_list_remove_link(cols->taborder, children);
  475. g_list_free(children);
  476. cols->taborder = g_list_append(cols->taborder, widget);
  477. break;
  478. }
  479. }
  480. void columns_vexpand(Columns *cols, GtkWidget *widget)
  481. {
  482. g_return_if_fail(cols != NULL);
  483. g_return_if_fail(IS_COLUMNS(cols));
  484. g_return_if_fail(widget != NULL);
  485. ColumnsChild *child = columns_find_child(cols, widget);
  486. g_return_if_fail(child != NULL);
  487. cols->vexpand = child;
  488. }
  489. /*
  490. * Override GtkContainer's focus movement so the user can
  491. * explicitly specify the tab order.
  492. */
  493. static gint columns_focus(FOCUS_METHOD_SUPERCLASS *super, GtkDirectionType dir)
  494. {
  495. Columns *cols;
  496. GList *pos;
  497. GtkWidget *focuschild;
  498. g_return_val_if_fail(super != NULL, false);
  499. g_return_val_if_fail(IS_COLUMNS(super), false);
  500. cols = COLUMNS(super);
  501. if (!gtk_widget_is_drawable(GTK_WIDGET(cols)) ||
  502. !gtk_widget_is_sensitive(GTK_WIDGET(cols)))
  503. return false;
  504. if (!gtk_widget_get_can_focus(GTK_WIDGET(cols)) &&
  505. (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
  506. focuschild = gtk_container_get_focus_child(GTK_CONTAINER(cols));
  507. gtk_container_set_focus_child(GTK_CONTAINER(cols), NULL);
  508. if (dir == GTK_DIR_TAB_FORWARD)
  509. pos = cols->taborder;
  510. else
  511. pos = g_list_last(cols->taborder);
  512. while (pos) {
  513. GtkWidget *child = pos->data;
  514. if (focuschild) {
  515. if (focuschild == child) {
  516. focuschild = NULL; /* now we can start looking in here */
  517. if (gtk_widget_is_drawable(child) &&
  518. GTK_IS_CONTAINER(child) &&
  519. !gtk_widget_has_focus(child)) {
  520. if (CHILD_FOCUS(child, dir))
  521. return true;
  522. }
  523. }
  524. } else if (gtk_widget_is_drawable(child)) {
  525. if (GTK_IS_CONTAINER(child)) {
  526. if (CHILD_FOCUS(child, dir))
  527. return true;
  528. } else if (gtk_widget_get_can_focus(child)) {
  529. gtk_widget_grab_focus(child);
  530. return true;
  531. }
  532. }
  533. if (dir == GTK_DIR_TAB_FORWARD)
  534. pos = pos->next;
  535. else
  536. pos = pos->prev;
  537. }
  538. return false;
  539. } else
  540. return columns_inherited_focus(super, dir);
  541. }
  542. /*
  543. * Underlying parts of the layout algorithm, to compute the Columns
  544. * container's width or height given the widths or heights of its
  545. * children. These will be called in various ways with different
  546. * notions of width and height in use, so we abstract them out and
  547. * pass them a 'get width' or 'get height' function pointer.
  548. */
  549. typedef gint (*widget_dim_fn_t)(ColumnsChild *child);
  550. static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width)
  551. {
  552. ColumnsChild *child;
  553. GList *children;
  554. gint i, ncols, colspan, retwidth, childwidth;
  555. const gint *percentages;
  556. static const gint onecol[] = { 100 };
  557. #ifdef COLUMNS_WIDTH_DIAGNOSTICS
  558. printf("compute_width(%p): start\n", cols);
  559. #endif
  560. retwidth = 0;
  561. ncols = 1;
  562. percentages = onecol;
  563. for (children = cols->children;
  564. children && (child = children->data);
  565. children = children->next) {
  566. if (!child->widget) {
  567. /* Column reconfiguration. */
  568. ncols = child->ncols;
  569. percentages = child->percentages;
  570. continue;
  571. }
  572. /* Only take visible widgets into account. */
  573. if (!gtk_widget_get_visible(child->widget))
  574. continue;
  575. childwidth = get_width(child);
  576. colspan = child->colspan ? child->colspan : ncols-child->colstart;
  577. assert(colspan > 0);
  578. #ifdef COLUMNS_WIDTH_DIAGNOSTICS
  579. printf("compute_width(%p): ", cols);
  580. if (GTK_IS_LABEL(child->widget))
  581. printf("label %p '%s' wrap=%s: ", child->widget,
  582. gtk_label_get_text(GTK_LABEL(child->widget)),
  583. (gtk_label_get_line_wrap(GTK_LABEL(child->widget))
  584. ? "true" : "false"));
  585. else
  586. printf("widget %p: ", child->widget);
  587. {
  588. gint min, nat;
  589. gtk_widget_get_preferred_width(child->widget, &min, &nat);
  590. printf("minwidth=%d natwidth=%d ", min, nat);
  591. }
  592. printf("thiswidth=%d span=%d\n", childwidth, colspan);
  593. #endif
  594. /*
  595. * To compute width: we know that childwidth + cols->spacing
  596. * needs to equal a certain percentage of the full width of
  597. * the container. So we work this value out, figure out how
  598. * wide the container will need to be to make that percentage
  599. * of it equal to that width, and ensure our returned width is
  600. * at least that much. Very simple really.
  601. */
  602. {
  603. int percent, thiswid, fullwid;
  604. percent = 0;
  605. for (i = 0; i < colspan; i++)
  606. percent += percentages[child->colstart+i];
  607. thiswid = childwidth + cols->spacing;
  608. /*
  609. * Since childwidth is (at least sometimes) the _minimum_
  610. * size the child needs, we must ensure that it gets _at
  611. * least_ that size. Hence, when scaling thiswid up to
  612. * fullwid, we must round up, which means adding percent-1
  613. * before dividing by percent.
  614. */
  615. fullwid = (thiswid * 100 + percent - 1) / percent;
  616. #ifdef COLUMNS_WIDTH_DIAGNOSTICS
  617. printf("compute_width(%p): after %p, thiswid=%d fullwid=%d\n",
  618. cols, child->widget, thiswid, fullwid);
  619. #endif
  620. /*
  621. * The above calculation assumes every widget gets
  622. * cols->spacing on the right. So we subtract
  623. * cols->spacing here to account for the extra load of
  624. * spacing on the right.
  625. */
  626. if (retwidth < fullwid - cols->spacing)
  627. retwidth = fullwid - cols->spacing;
  628. }
  629. }
  630. retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
  631. #ifdef COLUMNS_WIDTH_DIAGNOSTICS
  632. printf("compute_width(%p): done, returning %d\n", cols, retwidth);
  633. #endif
  634. return retwidth;
  635. }
  636. static void columns_alloc_horiz(Columns *cols, gint ourwidth,
  637. widget_dim_fn_t get_width)
  638. {
  639. ColumnsChild *child;
  640. GList *children;
  641. gint i, ncols, colspan, border, *colxpos, childwidth;
  642. border = gtk_container_get_border_width(GTK_CONTAINER(cols));
  643. ncols = 1;
  644. /* colxpos gives the starting x position of each column.
  645. * We supply n+1 of them, so that we can find the RH edge easily.
  646. * All ending x positions are expected to be adjusted afterwards by
  647. * subtracting the spacing. */
  648. colxpos = g_new(gint, 2);
  649. colxpos[0] = 0;
  650. colxpos[1] = ourwidth - 2*border + cols->spacing;
  651. for (children = cols->children;
  652. children && (child = children->data);
  653. children = children->next) {
  654. if (!child->widget) {
  655. gint percent;
  656. /* Column reconfiguration. */
  657. ncols = child->ncols;
  658. colxpos = g_renew(gint, colxpos, ncols + 1);
  659. colxpos[0] = 0;
  660. percent = 0;
  661. for (i = 0; i < ncols; i++) {
  662. percent += child->percentages[i];
  663. colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing)
  664. * percent / 100);
  665. }
  666. continue;
  667. }
  668. /* Only take visible widgets into account. */
  669. if (!gtk_widget_get_visible(child->widget))
  670. continue;
  671. childwidth = get_width(child);
  672. colspan = child->colspan ? child->colspan : ncols-child->colstart;
  673. /*
  674. * Starting x position is cols[colstart].
  675. * Ending x position is cols[colstart+colspan] - spacing.
  676. *
  677. * Unless we're forcing left, in which case the width is
  678. * exactly the requisition width.
  679. */
  680. child->x = colxpos[child->colstart];
  681. if (child->force_left)
  682. child->w = childwidth;
  683. else
  684. child->w = (colxpos[child->colstart+colspan] -
  685. colxpos[child->colstart] - cols->spacing);
  686. }
  687. g_free(colxpos);
  688. }
  689. static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height)
  690. {
  691. ColumnsChild *child;
  692. GList *children;
  693. gint i, ncols, colspan, *colypos, retheight, childheight;
  694. retheight = cols->spacing;
  695. ncols = 1;
  696. colypos = g_new(gint, 1);
  697. colypos[0] = 0;
  698. for (children = cols->children;
  699. children && (child = children->data);
  700. children = children->next) {
  701. if (!child->widget) {
  702. /* Column reconfiguration. */
  703. for (i = 1; i < ncols; i++) {
  704. if (colypos[0] < colypos[i])
  705. colypos[0] = colypos[i];
  706. }
  707. ncols = child->ncols;
  708. colypos = g_renew(gint, colypos, ncols);
  709. for (i = 1; i < ncols; i++)
  710. colypos[i] = colypos[0];
  711. continue;
  712. }
  713. /* Only take visible widgets into account. */
  714. if (!gtk_widget_get_visible(child->widget))
  715. continue;
  716. childheight = get_height(child);
  717. for (ColumnsChild *ch = child->valign_next; ch != child;
  718. ch = ch->valign_next) {
  719. gint childheight2 = get_height(ch);
  720. if (childheight < childheight2)
  721. childheight = childheight2;
  722. }
  723. colspan = child->colspan ? child->colspan : ncols-child->colstart;
  724. /*
  725. * To compute height: the widget's top will be positioned at
  726. * the largest y value so far reached in any of the columns it
  727. * crosses. Then it will go down by childheight plus padding;
  728. * and the point it reaches at the bottom is the new y value
  729. * in all those columns, and minus the padding it is also a
  730. * lower bound on our own height.
  731. */
  732. {
  733. int topy, boty;
  734. topy = 0;
  735. for (i = 0; i < colspan; i++) {
  736. if (topy < colypos[child->colstart+i])
  737. topy = colypos[child->colstart+i];
  738. }
  739. boty = topy + childheight + cols->spacing;
  740. for (i = 0; i < colspan; i++) {
  741. colypos[child->colstart+i] = boty;
  742. }
  743. if (retheight < boty - cols->spacing)
  744. retheight = boty - cols->spacing;
  745. }
  746. }
  747. retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
  748. g_free(colypos);
  749. return retheight;
  750. }
  751. static void columns_alloc_vert(Columns *cols, gint ourheight,
  752. widget_dim_fn_t get_height)
  753. {
  754. ColumnsChild *child;
  755. GList *children;
  756. gint i, ncols, colspan, *colypos, realheight, fakeheight, vexpand_extra;
  757. if (cols->vexpand) {
  758. gint minheight = columns_compute_height(cols, get_height);
  759. vexpand_extra = ourheight - minheight;
  760. } else {
  761. vexpand_extra = 0;
  762. }
  763. ncols = 1;
  764. /* As in size_request, colypos is the lowest y reached in each column. */
  765. colypos = g_new(gint, 1);
  766. colypos[0] = 0;
  767. for (children = cols->children;
  768. children && (child = children->data);
  769. children = children->next)
  770. child->visited = false;
  771. /*
  772. * Main layout loop. In this loop, vertically aligned controls are
  773. * only half dealt with: we assign each one enough _height_ to
  774. * match the others in its group, but we don't adjust its y
  775. * coordinates yet.
  776. */
  777. for (children = cols->children;
  778. children && (child = children->data);
  779. children = children->next) {
  780. if (!child->widget) {
  781. /* Column reconfiguration. */
  782. for (i = 1; i < ncols; i++) {
  783. if (colypos[0] < colypos[i])
  784. colypos[0] = colypos[i];
  785. }
  786. ncols = child->ncols;
  787. colypos = g_renew(gint, colypos, ncols);
  788. for (i = 1; i < ncols; i++)
  789. colypos[i] = colypos[0];
  790. continue;
  791. }
  792. /* Only take visible widgets into account. */
  793. if (!gtk_widget_get_visible(child->widget))
  794. continue;
  795. int ymin = 0;
  796. realheight = get_height(child);
  797. if (child == cols->vexpand)
  798. realheight += vexpand_extra;
  799. fakeheight = realheight;
  800. for (ColumnsChild *ch = child->valign_next; ch != child;
  801. ch = ch->valign_next) {
  802. gint childheight2 = get_height(ch);
  803. if (fakeheight < childheight2)
  804. fakeheight = childheight2;
  805. if (ch->visited && ymin < ch->y)
  806. ymin = ch->y;
  807. }
  808. colspan = child->colspan ? child->colspan : ncols-child->colstart;
  809. /*
  810. * To compute height: the widget's top will be positioned
  811. * at the largest y value so far reached in any of the
  812. * columns it crosses. Then it will go down by creq.height
  813. * plus padding; and the point it reaches at the bottom is
  814. * the new y value in all those columns.
  815. */
  816. {
  817. int topy, boty;
  818. topy = ymin;
  819. for (i = 0; i < colspan; i++) {
  820. if (topy < colypos[child->colstart+i])
  821. topy = colypos[child->colstart+i];
  822. }
  823. child->y = topy;
  824. child->h = realheight;
  825. child->visited = true;
  826. boty = topy + fakeheight + cols->spacing;
  827. for (i = 0; i < colspan; i++) {
  828. colypos[child->colstart+i] = boty;
  829. }
  830. }
  831. }
  832. /*
  833. * Now make a separate pass that deals with vertical alignment by
  834. * moving controls downwards based on the difference between their
  835. * own height and the largest height of anything in their group.
  836. */
  837. for (children = cols->children;
  838. children && (child = children->data);
  839. children = children->next) {
  840. if (!child->widget)
  841. continue;
  842. if (!gtk_widget_get_visible(child->widget))
  843. continue;
  844. fakeheight = realheight = child->h;
  845. for (ColumnsChild *ch = child->valign_next; ch != child;
  846. ch = ch->valign_next) {
  847. if (fakeheight < ch->h)
  848. fakeheight = ch->h;
  849. }
  850. child->y += fakeheight/2 - realheight/2;
  851. }
  852. g_free(colypos);
  853. }
  854. /*
  855. * Now here comes the interesting bit. The actual layout part is
  856. * done in the following two functions:
  857. *
  858. * columns_size_request() examines the list of widgets held in the
  859. * Columns, and returns a requisition stating the absolute minimum
  860. * size it can bear to be.
  861. *
  862. * columns_size_allocate() is given an allocation telling it what
  863. * size the whole container is going to be, and it calls
  864. * gtk_widget_size_allocate() on all of its (visible) children to
  865. * set their size and position relative to the top left of the
  866. * container.
  867. */
  868. #if !GTK_CHECK_VERSION(3,0,0)
  869. static gint columns_gtk2_get_width(ColumnsChild *child)
  870. {
  871. GtkRequisition creq;
  872. gtk_widget_size_request(child->widget, &creq);
  873. return creq.width;
  874. }
  875. static gint columns_gtk2_get_height(ColumnsChild *child)
  876. {
  877. GtkRequisition creq;
  878. gtk_widget_size_request(child->widget, &creq);
  879. return creq.height;
  880. }
  881. static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
  882. {
  883. Columns *cols;
  884. g_return_if_fail(widget != NULL);
  885. g_return_if_fail(IS_COLUMNS(widget));
  886. g_return_if_fail(req != NULL);
  887. cols = COLUMNS(widget);
  888. req->width = columns_compute_width(cols, columns_gtk2_get_width);
  889. req->height = columns_compute_height(cols, columns_gtk2_get_height);
  890. }
  891. static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
  892. {
  893. Columns *cols;
  894. ColumnsChild *child;
  895. GList *children;
  896. gint border;
  897. g_return_if_fail(widget != NULL);
  898. g_return_if_fail(IS_COLUMNS(widget));
  899. g_return_if_fail(alloc != NULL);
  900. cols = COLUMNS(widget);
  901. gtk_widget_set_allocation(widget, alloc);
  902. border = gtk_container_get_border_width(GTK_CONTAINER(cols));
  903. columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width);
  904. columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height);
  905. for (children = cols->children;
  906. children && (child = children->data);
  907. children = children->next) {
  908. if (child->widget && gtk_widget_get_visible(child->widget)) {
  909. GtkAllocation call;
  910. call.x = alloc->x + border + child->x;
  911. call.y = alloc->y + border + child->y;
  912. call.width = child->w;
  913. call.height = child->h;
  914. gtk_widget_size_allocate(child->widget, &call);
  915. }
  916. }
  917. }
  918. #else /* GTK_CHECK_VERSION(3,0,0) */
  919. static gint columns_gtk3_get_min_width(ColumnsChild *child)
  920. {
  921. gint ret;
  922. gtk_widget_get_preferred_width(child->widget, &ret, NULL);
  923. return ret;
  924. }
  925. static gint columns_gtk3_get_nat_width(ColumnsChild *child)
  926. {
  927. gint ret;
  928. if ((GTK_IS_LABEL(child->widget) &&
  929. gtk_label_get_line_wrap(GTK_LABEL(child->widget))) ||
  930. GTK_IS_ENTRY(child->widget)) {
  931. /*
  932. * We treat wrapping GtkLabels as a special case in this
  933. * layout class, because the whole point of those is that I
  934. * _don't_ want them to take up extra horizontal space for
  935. * long text, but instead to wrap it to whatever size is used
  936. * by the rest of the layout.
  937. *
  938. * GtkEntry gets similar treatment, because in OS X GTK I've
  939. * found that it requests a natural width regardless of the
  940. * output of gtk_entry_set_width_chars.
  941. */
  942. gtk_widget_get_preferred_width(child->widget, &ret, NULL);
  943. } else {
  944. gtk_widget_get_preferred_width(child->widget, NULL, &ret);
  945. }
  946. return ret;
  947. }
  948. static gint columns_gtk3_get_minfh_width(ColumnsChild *child)
  949. {
  950. gint ret;
  951. gtk_widget_get_preferred_width_for_height(child->widget, child->h,
  952. &ret, NULL);
  953. return ret;
  954. }
  955. static gint columns_gtk3_get_natfh_width(ColumnsChild *child)
  956. {
  957. gint ret;
  958. gtk_widget_get_preferred_width_for_height(child->widget, child->h,
  959. NULL, &ret);
  960. return ret;
  961. }
  962. static gint columns_gtk3_get_min_height(ColumnsChild *child)
  963. {
  964. gint ret;
  965. gtk_widget_get_preferred_height(child->widget, &ret, NULL);
  966. return ret;
  967. }
  968. static gint columns_gtk3_get_nat_height(ColumnsChild *child)
  969. {
  970. gint ret;
  971. gtk_widget_get_preferred_height(child->widget, NULL, &ret);
  972. return ret;
  973. }
  974. static gint columns_gtk3_get_minfw_height(ColumnsChild *child)
  975. {
  976. gint ret;
  977. gtk_widget_get_preferred_height_for_width(child->widget, child->w,
  978. &ret, NULL);
  979. return ret;
  980. }
  981. static gint columns_gtk3_get_natfw_height(ColumnsChild *child)
  982. {
  983. gint ret;
  984. gtk_widget_get_preferred_height_for_width(child->widget, child->w,
  985. NULL, &ret);
  986. return ret;
  987. }
  988. static void columns_get_preferred_width(GtkWidget *widget,
  989. gint *min, gint *nat)
  990. {
  991. Columns *cols;
  992. g_return_if_fail(widget != NULL);
  993. g_return_if_fail(IS_COLUMNS(widget));
  994. cols = COLUMNS(widget);
  995. if (min)
  996. *min = columns_compute_width(cols, columns_gtk3_get_min_width);
  997. if (nat)
  998. *nat = columns_compute_width(cols, columns_gtk3_get_nat_width);
  999. }
  1000. static void columns_get_preferred_height(GtkWidget *widget,
  1001. gint *min, gint *nat)
  1002. {
  1003. Columns *cols;
  1004. g_return_if_fail(widget != NULL);
  1005. g_return_if_fail(IS_COLUMNS(widget));
  1006. cols = COLUMNS(widget);
  1007. if (min)
  1008. *min = columns_compute_height(cols, columns_gtk3_get_min_height);
  1009. if (nat)
  1010. *nat = columns_compute_height(cols, columns_gtk3_get_nat_height);
  1011. }
  1012. static void columns_get_preferred_width_for_height(GtkWidget *widget,
  1013. gint height,
  1014. gint *min, gint *nat)
  1015. {
  1016. Columns *cols;
  1017. g_return_if_fail(widget != NULL);
  1018. g_return_if_fail(IS_COLUMNS(widget));
  1019. cols = COLUMNS(widget);
  1020. /* FIXME: which one should the get-height function here be? */
  1021. columns_alloc_vert(cols, height, columns_gtk3_get_nat_height);
  1022. if (min)
  1023. *min = columns_compute_width(cols, columns_gtk3_get_minfh_width);
  1024. if (nat)
  1025. *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width);
  1026. }
  1027. static void columns_get_preferred_height_for_width(GtkWidget *widget,
  1028. gint width,
  1029. gint *min, gint *nat)
  1030. {
  1031. Columns *cols;
  1032. g_return_if_fail(widget != NULL);
  1033. g_return_if_fail(IS_COLUMNS(widget));
  1034. cols = COLUMNS(widget);
  1035. /* FIXME: which one should the get-height function here be? */
  1036. columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width);
  1037. if (min)
  1038. *min = columns_compute_height(cols, columns_gtk3_get_minfw_height);
  1039. if (nat)
  1040. *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height);
  1041. }
  1042. static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
  1043. {
  1044. Columns *cols;
  1045. ColumnsChild *child;
  1046. GList *children;
  1047. gint border;
  1048. g_return_if_fail(widget != NULL);
  1049. g_return_if_fail(IS_COLUMNS(widget));
  1050. g_return_if_fail(alloc != NULL);
  1051. cols = COLUMNS(widget);
  1052. gtk_widget_set_allocation(widget, alloc);
  1053. border = gtk_container_get_border_width(GTK_CONTAINER(cols));
  1054. columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width);
  1055. columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height);
  1056. for (children = cols->children;
  1057. children && (child = children->data);
  1058. children = children->next) {
  1059. if (child->widget && gtk_widget_get_visible(child->widget)) {
  1060. GtkAllocation call;
  1061. call.x = alloc->x + border + child->x;
  1062. call.y = alloc->y + border + child->y;
  1063. call.width = child->w;
  1064. call.height = child->h;
  1065. gtk_widget_size_allocate(child->widget, &call);
  1066. }
  1067. }
  1068. }
  1069. #endif