Specifying preferred line break opportunities in a GtkLabel

Is it possible to use Pango markup or something else to give a GtkLabel a list of positions, which should be of highest priority when choosing where to wrap the text? I know that can already be achieved using GtkFlowBox and multiple GtkLabel’s (or similar) but using just one GtkLabel would result in much simpler code. I’ve been eyeing the segment="sentence" attribute in particular, but I’m not sure how it works and if it does anything at all.

After looking through Pango’s and Gtk’s source code, I have determined that what I want is simply not there. So I made it myself:

import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from dataclasses import dataclass
from gi.repository import Gdk, Gtk

class SegmentedLabel(Gtk.Widget):
  def __init__(self, segment_props={}, **kwargs):
    super().__init__(layout_manager=self.Layout(), **kwargs)
    self.segment_props = segment_props

  def __del__(self):
    while (i := self.get_first_child()) is not None:
      i.unparent()

  def set_segments(self, segments):
    self.__del__()
    for i in segments:
      Gtk.Label(label=i, justify=Gtk.Justification.CENTER, **self.segment_props).set_parent(self)

  @property
  def children(self):
    i = self.get_first_child()
    while i is not None:
      yield i
      i = i.get_next_sibling()

  class Layout(Gtk.LayoutManager):
    def do_get_request_mode(self, _):
      return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH

    @dataclass
    class Row:
      width: int
      height: int
      segments: list[Gtk.Label]

    def get_rows(self, label, width):
      result = []
      row = self.Row(0, 0, [])
      for child in label.children:
        nat = child.get_preferred_size().natural_size

        if row.width + nat.width > width and row.segments:
          result.append(row)
          row = self.Row(0, 0, [])

        if nat.width > width:
          min, nat, _, _ = child.measure(Gtk.Orientation.VERTICAL, width)
          assert min == nat
          result.append(self.Row(width, min, [child]))
        else:
          row.width += nat.width
          row.height = max(row.height, nat.height)
          row.segments.append(child)

      if row.segments:
        result.append(row)
      return result

    def do_measure(self, label, orientation, width):
      if orientation == Gtk.Orientation.HORIZONTAL:
        assert width == -1
        min, nat = 0, 0
        for child in label.children:
          child_min, child_nat, _, _ = child.measure(orientation, -1)
          min = max(min, child_min)
          nat += child_nat
        return min, nat, -1, -1

      elif width == -1:
        min, nat = 0, 0
        for child in label.children:
          child_min, child_nat, _, _ = child.measure(orientation, -1)
          min = max(min, child_min)
          nat = max(nat, child_nat)
        return min, nat, -1, -1
      else:
        height = sum(row.height for row in self.get_rows(label, width))
        return height, height, -1, -1

    def do_allocate(self, label, width, height, _):
      rows = self.get_rows(label, width)
      rect = Gdk.Rectangle()
      rect.y = (height - sum(i.height for i in rows)) / 2
      for row in rows:
        rect.height = row.height
        if len(row.segments) == 1:
          rect.x = 0
          rect.width = width
          row.segments[0].size_allocate(rect, -1)
        else:
          rect.x = (width - row.width) / 2
          for segment in row.segments:
            rect.width = segment.get_preferred_size().natural_size.width
            segment.size_allocate(rect, -1)
            rect.x += rect.width
        rect.y += rect.height

class Application(Gtk.Application):
  def do_activate(self):
    window = Gtk.ApplicationWindow(application=self)
    # Need to keep a reference because Gtk.Window doesn't do that for us.
    self.label = SegmentedLabel(
      {
        'wrap': True,
        'use_markup': True,
      },
      margin_start=50,
      margin_end=50,
      margin_top=50,
      margin_bottom=50,
      css_classes=['frame'],
    )
    self.label.set_segments([
      'Question: ',
      '<i>What do you think of it?</i> ',
      'Answer: ',
      '<i>Lorem ipsum dolor sit amet.</i>',
    ])
    window.set_child(self.label)
    window.present()

Application(application_id='com.example.SegmentedLabel').run()

May this code be of use to anyone who encounters a similar problem.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.