2
* Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Flamingo Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.flamingo.internal.ui.common;
33
import java.beans.PropertyChangeEvent;
34
import java.beans.PropertyChangeListener;
37
import javax.swing.event.ChangeEvent;
38
import javax.swing.event.ChangeListener;
39
import javax.swing.plaf.ComponentUI;
40
import javax.swing.plaf.UIResource;
42
import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
43
import org.pushingpixels.flamingo.api.common.JCommandButtonPanel;
44
import org.pushingpixels.flamingo.api.common.JCommandButtonPanel.LayoutKind;
45
import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
48
* Basic UI for command button panel {@link JCommandButtonPanel}.
50
* @author Kirill Grouchnikov
52
public class BasicCommandButtonPanelUI extends CommandButtonPanelUI {
54
* The associated command button panel.
56
protected JCommandButtonPanel buttonPanel;
59
* Labels of the button panel groups.
61
protected JLabel[] groupLabels;
64
* Bounds of button panel groups.
66
protected Rectangle[] groupRects;
69
* Property change listener on {@link #buttonPanel}.
71
protected PropertyChangeListener propertyChangeListener;
74
* Change listener on {@link #buttonPanel}.
76
protected ChangeListener changeListener;
79
* Default insets of button panel groups.
81
protected static final Insets GROUP_INSETS = new Insets(4, 4, 4, 4);
86
* @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
88
public static ComponentUI createUI(JComponent c) {
89
return new BasicCommandButtonPanelUI();
95
* @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
98
public void installUI(JComponent c) {
99
this.buttonPanel = (JCommandButtonPanel) c;
107
* Installs defaults on the associated button panel.
109
protected void installDefaults() {
110
this.buttonPanel.setLayout(this.createLayoutManager());
111
Font currFont = this.buttonPanel.getFont();
112
if ((currFont == null) || (currFont instanceof UIResource)) {
113
Font titleFont = FlamingoUtilities.getFont(null,
114
"CommandButtonPanel.font", "Button.font", "Panel.font");
115
this.buttonPanel.setFont(titleFont);
120
* Installs sub-components on the associated button panel.
122
protected void installComponents() {
123
this.recomputeGroupHeaders();
127
* Installs listeners on the associated button panel.
129
protected void installListeners() {
130
this.propertyChangeListener = new PropertyChangeListener() {
132
public void propertyChange(PropertyChangeEvent evt) {
133
if ("maxButtonColumns".equals(evt.getPropertyName())
134
|| "maxButtonRows".equals(evt.getPropertyName())
135
|| "toShowGroupLabels".equals(evt.getPropertyName())) {
136
SwingUtilities.invokeLater(new Runnable() {
139
if (buttonPanel != null) {
140
recomputeGroupHeaders();
141
buttonPanel.revalidate();
142
buttonPanel.doLayout();
148
if ("layoutKind".equals(evt.getPropertyName())) {
149
SwingUtilities.invokeLater(new Runnable() {
152
if (buttonPanel != null) {
153
buttonPanel.setLayout(createLayoutManager());
154
buttonPanel.revalidate();
155
buttonPanel.doLayout();
162
this.buttonPanel.addPropertyChangeListener(this.propertyChangeListener);
164
this.changeListener = new ChangeListener() {
166
public void stateChanged(ChangeEvent e) {
167
recomputeGroupHeaders();
168
buttonPanel.revalidate();
169
buttonPanel.doLayout();
172
this.buttonPanel.addChangeListener(this.changeListener);
178
* @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
181
public void uninstallUI(JComponent c) {
184
uninstallListeners();
185
uninstallComponents();
187
this.buttonPanel = null;
191
* Uninstalls defaults from the associated button panel.
193
protected void uninstallDefaults() {
197
* Uninstalls sub-components from the associated button panel.
199
protected void uninstallComponents() {
200
if (this.groupLabels != null) {
201
for (JLabel groupLabel : this.groupLabels) {
202
this.buttonPanel.remove(groupLabel);
204
// for (JSeparator groupSeparator : this.groupSeparators) {
205
// this.buttonPanel.remove(groupSeparator);
211
* Uninstalls listeners from the associated button panel.
213
protected void uninstallListeners() {
215
.removePropertyChangeListener(this.propertyChangeListener);
216
this.propertyChangeListener = null;
218
this.buttonPanel.removeChangeListener(this.changeListener);
219
this.changeListener = null;
223
* Returns the layout manager for the associated button panel.
225
* @return The layout manager for the associated button panel.
227
protected LayoutManager createLayoutManager() {
228
if (this.buttonPanel.getLayoutKind() == LayoutKind.ROW_FILL)
229
return new RowFillLayout();
231
return new ColumnFillLayout();
237
* @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
238
* javax.swing.JComponent)
241
public void paint(Graphics g, JComponent c) {
242
Color bg = this.buttonPanel.getBackground();
244
g.fillRect(0, 0, c.getWidth(), c.getHeight());
246
for (int i = 0; i < this.buttonPanel.getGroupCount(); i++) {
247
Rectangle groupRect = this.groupRects[i];
248
this.paintGroupBackground(g, i, groupRect.x, groupRect.y,
249
groupRect.width, groupRect.height);
251
if (this.groupLabels[i].isVisible()) {
252
Rectangle groupTitleBackground = this.groupLabels[i]
254
this.paintGroupTitleBackground(g, i, groupRect.x,
255
groupTitleBackground.y - getGroupInsets().top,
256
groupRect.width, groupTitleBackground.height
257
+ getGroupInsets().top + getLayoutGap());
263
* Paints the background of the specified button panel group.
270
* X coordinate of the button group bounds.
272
* Y coordinate of the button group bounds.
274
* Width of the button group bounds.
276
* Height of the button group bounds.
278
protected void paintGroupBackground(Graphics g, int groupIndex, int x,
279
int y, int width, int height) {
280
Color c = this.buttonPanel.getBackground();
281
if ((c == null) || (c instanceof UIResource)) {
282
c = UIManager.getColor("Panel.background");
284
c = new Color(190, 190, 190);
285
if (groupIndex % 2 == 1) {
287
c = new Color((int) (c.getRed() * coef),
288
(int) (c.getGreen() * coef), (int) (c.getBlue() * coef));
292
g.fillRect(x, y, width, height);
296
* Paints the background of the title of specified button panel group.
303
* X coordinate of the button group title bounds.
305
* Y coordinate of the button group title bounds.
307
* Width of the button group title bounds.
309
* Height of the button group title bounds.
311
protected void paintGroupTitleBackground(Graphics g, int groupIndex, int x,
312
int y, int width, int height) {
313
FlamingoUtilities.renderSurface(g, this.buttonPanel, new Rectangle(x,
314
y, width, height), false, (groupIndex > 0), true);
318
* Returns the height of the group title strip.
322
* @return The height of the title strip of the specified group.
324
protected int getGroupTitleHeight(int groupIndex) {
325
return this.groupLabels[groupIndex].getPreferredSize().height;
329
* Returns the insets of button panel groups.
331
* @return The insets of button panel groups.
333
protected Insets getGroupInsets() {
338
* Row-fill layout for the button panel.
340
* @author Kirill Grouchnikov
342
protected class RowFillLayout implements LayoutManager {
346
* @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
347
* java.awt.Component)
350
public void addLayoutComponent(String name, Component comp) {
356
* @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
359
public void removeLayoutComponent(Component comp) {
365
* @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
368
public void layoutContainer(Container parent) {
369
Insets bInsets = parent.getInsets();
370
Insets groupInsets = getGroupInsets();
371
int left = bInsets.left;
372
int right = bInsets.right;
376
JCommandButtonPanel panel = (JCommandButtonPanel) parent;
377
boolean ltr = panel.getComponentOrientation().isLeftToRight();
379
// compute max width of buttons
380
int maxButtonWidth = 0;
381
int maxButtonHeight = 0;
382
int groupCount = panel.getGroupCount();
383
for (int i = 0; i < groupCount; i++) {
384
for (AbstractCommandButton button : panel.getGroupButtons(i)) {
385
maxButtonWidth = Math.max(maxButtonWidth, button
386
.getPreferredSize().width);
387
maxButtonHeight = Math.max(maxButtonHeight, button
388
.getPreferredSize().height);
391
groupRects = new Rectangle[groupCount];
393
int gap = getLayoutGap();
394
int maxWidth = parent.getWidth() - bInsets.left - bInsets.right
395
- groupInsets.left - groupInsets.right;
396
// for N buttons, there are N-1 gaps. Add the gap to the
397
// available width and divide by the max button width + gap.
398
int buttonsInRow = (maxButtonWidth == 0) ? 0 : (maxWidth + gap)
399
/ (maxButtonWidth + gap);
400
int maxButtonColumnsToUse = panel.getMaxButtonColumns();
401
if (maxButtonColumnsToUse > 0) {
402
buttonsInRow = Math.min(buttonsInRow, maxButtonColumnsToUse);
405
// System.out.println("Layout : " + buttonsInRow);
406
for (int i = 0; i < groupCount; i++) {
408
y += groupInsets.top;
410
JLabel groupLabel = groupLabels[i];
411
if (buttonPanel.isToShowGroupLabels()) {
412
int labelWidth = groupLabel.getPreferredSize().width;
413
int labelHeight = getGroupTitleHeight(i);
414
if (groupLabel.getComponentOrientation().isLeftToRight()) {
415
groupLabel.setBounds(left + groupInsets.left, y,
416
labelWidth, labelHeight);
418
groupLabel.setBounds(parent.getWidth() - right
419
- groupInsets.right - labelWidth, y,
420
labelWidth, labelHeight);
422
y += labelHeight + gap;
425
int buttonRows = (buttonsInRow == 0) ? 0 : (int) (Math
426
.ceil((double) panel.getGroupButtons(i).size()
428
if (maxButtonColumnsToUse > 0) {
430
.min(buttonsInRow, maxButtonColumnsToUse);
433
// spread the buttons so that we don't have extra space
435
int actualButtonWidth = (buttonRows > 1) ? (maxWidth - (buttonsInRow - 1)
439
if (maxButtonColumnsToUse == 1)
440
actualButtonWidth = maxWidth;
443
int currX = left + groupInsets.left;
444
for (AbstractCommandButton button : panel
445
.getGroupButtons(i)) {
446
int endX = currX + actualButtonWidth;
447
if (endX > (parent.getWidth() - right - groupInsets.right)) {
448
currX = left + groupInsets.left;
449
y += maxButtonHeight;
452
button.setBounds(currX, y, actualButtonWidth,
454
// System.out.println(button.getText() + ":"
455
// + button.isVisible() + ":" + button.getBounds());
456
currX += actualButtonWidth;
460
int currX = parent.getWidth() - right - groupInsets.right;
461
for (AbstractCommandButton button : panel
462
.getGroupButtons(i)) {
463
int startX = currX - actualButtonWidth;
464
if (startX < (left + groupInsets.left)) {
465
currX = parent.getWidth() - right
467
y += maxButtonHeight;
470
button.setBounds(currX - actualButtonWidth, y,
471
actualButtonWidth, maxButtonHeight);
472
// System.out.println(button.getText() + ":"
473
// + button.isVisible() + ":" + button.getBounds());
474
currX -= actualButtonWidth;
479
y += maxButtonHeight + groupInsets.bottom;
480
int bottomGroupY = y;
481
groupRects[i] = new Rectangle(left, topGroupY, (parent
483
- left - right), (bottomGroupY - topGroupY));
490
* @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
493
public Dimension minimumLayoutSize(Container parent) {
494
return new Dimension(20, 20);
500
* @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
503
public Dimension preferredLayoutSize(Container parent) {
504
JCommandButtonPanel panel = (JCommandButtonPanel) parent;
506
int maxButtonColumnsToUse = panel.getMaxButtonColumns();
508
Insets bInsets = parent.getInsets();
509
Insets groupInsets = getGroupInsets();
510
int insetsWidth = bInsets.left + groupInsets.left + bInsets.right
513
// compute max width of buttons
514
int maxButtonWidth = 0;
515
int maxButtonHeight = 0;
516
int groupCount = panel.getGroupCount();
517
for (int i = 0; i < groupCount; i++) {
518
for (AbstractCommandButton button : panel.getGroupButtons(i)) {
519
maxButtonWidth = Math.max(maxButtonWidth, button
520
.getPreferredSize().width);
521
maxButtonHeight = Math.max(maxButtonHeight, button
522
.getPreferredSize().height);
527
int gap = getLayoutGap();
528
boolean usePanelWidth = (maxButtonColumnsToUse <= 0);
529
int availableWidth = panel.getWidth();
530
availableWidth -= insetsWidth;
533
// this hasn't been set. Compute using the available
535
maxButtonColumnsToUse = (availableWidth + gap)
536
/ (maxButtonWidth + gap);
538
int height = bInsets.top + bInsets.bottom;
539
// System.out.print(height + "[" + maxButtonColumnsToUse + "]");
540
for (int i = 0; i < groupCount; i++) {
541
if (groupLabels[i].isVisible()) {
542
height += (getGroupTitleHeight(i) + gap);
545
height += (groupInsets.top + groupInsets.bottom);
547
int buttonRows = (int) (Math.ceil((double) panel
548
.getGroupButtons(i).size()
549
/ maxButtonColumnsToUse));
550
height += buttonRows * maxButtonHeight + (buttonRows - 1) * gap;
551
// System.out.print(" " + height);
553
int prefWidth = usePanelWidth ? availableWidth
554
: maxButtonColumnsToUse * maxButtonWidth
555
+ (maxButtonColumnsToUse - 1) * gap + bInsets.left
556
+ bInsets.right + groupInsets.left
558
// System.out.println(" : " + height);
559
return new Dimension(Math.max(10, prefWidth), Math.max(10, height));
564
* Column-fill layout for the button panel.
566
* @author Kirill Grouchnikov
568
protected class ColumnFillLayout implements LayoutManager {
572
* @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
573
* java.awt.Component)
576
public void addLayoutComponent(String name, Component comp) {
582
* @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
585
public void removeLayoutComponent(Component comp) {
591
* @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
594
public void layoutContainer(Container parent) {
595
Insets bInsets = parent.getInsets();
596
Insets groupInsets = getGroupInsets();
597
int top = bInsets.top;
598
int bottom = bInsets.bottom;
600
JCommandButtonPanel panel = (JCommandButtonPanel) parent;
601
boolean ltr = panel.getComponentOrientation().isLeftToRight();
603
// compute max width of buttons
604
int maxButtonWidth = 0;
605
int maxButtonHeight = 0;
606
int groupCount = panel.getGroupCount();
607
for (int i = 0; i < groupCount; i++) {
608
for (AbstractCommandButton button : panel.getGroupButtons(i)) {
609
maxButtonWidth = Math.max(maxButtonWidth, button
610
.getPreferredSize().width);
611
maxButtonHeight = Math.max(maxButtonHeight, button
612
.getPreferredSize().height);
615
groupRects = new Rectangle[groupCount];
617
int gap = getLayoutGap();
618
int maxHeight = parent.getHeight() - bInsets.top - bInsets.bottom
619
- groupInsets.top - groupInsets.bottom;
620
// for N buttons, there are N-1 gaps. Add the gap to the
621
// available width and divide by the max button width + gap.
622
int buttonsInRow = (maxButtonHeight == 0) ? 0 : (maxHeight + gap)
623
/ (maxButtonHeight + gap);
626
int x = bInsets.left + groupInsets.left;
627
for (int i = 0; i < groupCount; i++) {
629
x += groupInsets.left;
630
int currY = top + groupInsets.top;
632
int buttonColumns = (buttonsInRow == 0) ? 0 : (int) (Math
633
.ceil((double) panel.getGroupButtons(i).size()
635
// spread the buttons so that we don't have extra space
637
int actualButtonHeight = (buttonColumns > 1) ? (maxHeight - (buttonsInRow - 1)
642
for (AbstractCommandButton button : panel
643
.getGroupButtons(i)) {
644
int endY = currY + actualButtonHeight;
645
if (endY > (parent.getHeight() - bottom - groupInsets.bottom)) {
646
currY = top + groupInsets.top;
650
button.setBounds(x, currY, maxButtonWidth,
652
// System.out.println(button.getText() + ":"
653
// + button.isVisible() + ":" + button.getBounds());
654
currY += actualButtonHeight;
657
x += maxButtonWidth + groupInsets.bottom;
659
groupRects[i] = new Rectangle(leftGroupX, top,
660
(rightGroupX - leftGroupX), (parent.getHeight()
664
int x = panel.getWidth() - bInsets.right - groupInsets.right;
665
for (int i = 0; i < groupCount; i++) {
667
x -= groupInsets.left;
668
int currY = top + groupInsets.top;
670
int buttonColumns = (buttonsInRow == 0) ? 0 : (int) (Math
671
.ceil((double) panel.getGroupButtons(i).size()
673
// spread the buttons so that we don't have extra space
675
int actualButtonHeight = (buttonColumns > 1) ? (maxHeight - (buttonsInRow - 1)
680
for (AbstractCommandButton button : panel
681
.getGroupButtons(i)) {
682
int endY = currY + actualButtonHeight;
683
if (endY > (parent.getHeight() - bottom - groupInsets.bottom)) {
684
currY = top + groupInsets.top;
688
button.setBounds(x - maxButtonWidth, currY,
689
maxButtonWidth, actualButtonHeight);
690
// System.out.println(button.getText() + ":"
691
// + button.isVisible() + ":" + button.getBounds());
692
currY += actualButtonHeight;
695
x -= (maxButtonWidth + groupInsets.bottom);
697
groupRects[i] = new Rectangle(leftGroupX, top,
698
(rightGroupX - leftGroupX), (parent.getHeight()
707
* @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
710
public Dimension minimumLayoutSize(Container parent) {
711
return new Dimension(20, 20);
717
* @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
720
public Dimension preferredLayoutSize(Container parent) {
721
JCommandButtonPanel panel = (JCommandButtonPanel) parent;
723
int maxButtonRowsToUse = panel.getMaxButtonRows();
725
Insets bInsets = parent.getInsets();
726
Insets groupInsets = getGroupInsets();
727
int insetsHeight = bInsets.top + groupInsets.top + bInsets.bottom
728
+ groupInsets.bottom;
730
// compute max width of buttons
731
int maxButtonWidth = 0;
732
int maxButtonHeight = 0;
733
int groupCount = panel.getGroupCount();
734
for (int i = 0; i < groupCount; i++) {
735
for (AbstractCommandButton button : panel.getGroupButtons(i)) {
736
maxButtonWidth = Math.max(maxButtonWidth, button
737
.getPreferredSize().width);
738
maxButtonHeight = Math.max(maxButtonHeight, button
739
.getPreferredSize().height);
744
int gap = getLayoutGap();
745
boolean usePanelHeight = (maxButtonRowsToUse <= 0);
747
int availableHeight = panel.getHeight();
748
availableHeight -= insetsHeight;
749
if (usePanelHeight) {
750
// this hasn't been set. Compute using the available
752
maxButtonRowsToUse = (availableHeight + gap)
753
/ (maxButtonHeight + gap);
755
// go over all groups and see how many columns each one needs
756
int width = bInsets.left + bInsets.right;
757
for (int i = 0; i < groupCount; i++) {
758
width += (groupInsets.left + groupInsets.right);
760
int buttonColumns = (int) (Math.ceil((double) panel
761
.getGroupButtons(i).size()
762
/ maxButtonRowsToUse));
763
width += buttonColumns * maxButtonWidth + (buttonColumns - 1)
766
int prefHeight = usePanelHeight ? availableHeight
767
: maxButtonRowsToUse * maxButtonWidth
768
+ (maxButtonRowsToUse - 1) * gap + bInsets.top
769
+ bInsets.bottom + groupInsets.top
770
+ groupInsets.bottom;
771
return new Dimension(Math.max(10, width), Math.max(10, prefHeight));
776
* Returns the layout gap for button panel components.
778
* @return The layout gap for button panel components.
780
protected int getLayoutGap() {
785
* Recomputes the components for button group headers.
787
protected void recomputeGroupHeaders() {
788
if (this.groupLabels != null) {
789
for (JLabel groupLabel : this.groupLabels) {
790
this.buttonPanel.remove(groupLabel);
794
int groupCount = this.buttonPanel.getGroupCount();
795
this.groupLabels = new JLabel[groupCount];
796
for (int i = 0; i < groupCount; i++) {
797
this.groupLabels[i] = new JLabel(this.buttonPanel
798
.getGroupTitleAt(i));
799
this.groupLabels[i].setComponentOrientation(this.buttonPanel
800
.getComponentOrientation());
802
this.buttonPanel.add(this.groupLabels[i]);
804
this.groupLabels[i].setVisible(this.buttonPanel
805
.isToShowGroupLabels());
810
* Returns the preferred size of the associated button panel for the
811
* specified parameters.
813
* @param buttonVisibleRows
814
* Target number of visible button rows.
815
* @param titleVisibleRows
816
* Target number of visible group title rows.
817
* @return The preferred size of the associated button panel for the
818
* specified parameters.
820
public int getPreferredHeight(int buttonVisibleRows, int titleVisibleRows) {
821
Insets bInsets = this.buttonPanel.getInsets();
822
Insets groupInsets = getGroupInsets();
823
int maxButtonHeight = 0;
824
int groupCount = this.buttonPanel.getGroupCount();
825
for (int i = 0; i < groupCount; i++) {
826
for (AbstractCommandButton button : this.buttonPanel
827
.getGroupButtons(i)) {
828
maxButtonHeight = Math.max(maxButtonHeight, button
829
.getPreferredSize().height);
834
int gap = getLayoutGap();
837
int totalHeight = bInsets.top + bInsets.bottom;
838
// height of icon rows
839
totalHeight += buttonVisibleRows * maxButtonHeight;
840
// gaps between icon rows
841
totalHeight += (buttonVisibleRows - 1) * gap;
843
totalHeight += titleVisibleRows * getGroupTitleHeight(0);
845
totalHeight += (titleVisibleRows - 1)
846
* (groupInsets.top + groupInsets.bottom);