StefanSalewski / gintro

High level GObject-Introspection based GTK3/GTK4 bindings for Nim language
MIT License
296 stars 20 forks source link

ComboBoxWithModelAndEntry can't display text from model #69

Open dmknght opened 4 years ago

dmknght commented 4 years ago

I'm writing a simple program that the ComboBox has auto completion like the Entry. I completed this but i'm having 2 issues:

  1. When i click on the button in the right of the box, it shows empty rows (rows are still there but it has no text). I also get this error in terminal: Gtk-CRITICAL **: 04:36:12.088: gtk_entry_set_text: assertion 'text != NULL' failed. Can you please check my code and help me correct this issue?
  2. The adding item to ListStore is showing error GLib-GObject-WARNING **: 04:36:05.559: ../../../gobject/gvalue.c:180: cannot initialize GValue with type 'gchararray', the value has already been initialized as 'gchararray'. I completed this code by combining ListView example and google. I'm using nim 1.2.0 from Debian and gintro 0.7.3. My code:
    
    import strutils
    import re
    import gintro / [gtk, gobject, glib]

proc convertLayout(data: string): string =

[

Convert all weird text to "GB - English (UK)"

]# let findAllText = findAll(data, re"([a-zA-Z0-9_\/(),-]+)") result = findAllText[0].toUpper() & " -" for text in findAllText[1 .. findAllText.high]: result &= " " & text

proc getLayouts(): seq[string] =

[

Read data from path and parse ! layout section

]# const path = "/usr/share/X11/xkb/rules/xorg.lst"

We add a variable to check when we start parsing lines

After parse, we stop after ! character

var flag = false for line in lines(path):

If the section is not ! Layout and we parsed (checked by flag)

if line.startsWith("!") and line != "! layout" and flag == true:
  return result
# We start parsing layout section
elif flag == false and line == "! layout":
  flag = true
# It should be sub sections
else:
  # This sub section should belong to layout
  if flag == true and not isEmptyOrWhitespace(line):
    result.add(convertLayout(line))

proc createArea(boxMain: Box) =

[

Steps
https://athenajc.gitbooks.io/python-gtk-3-api/content/gtk-group/gtkentrycompletion.html
https://stackoverflow.com/a/39426800
Create ListStore
Add items to ListStore == BUG == Items don't show text in ComboBoxTextWithEntry
Create EntryCompletion
set entrycompletion model to liststore
set TextColumn of EntryCompletion = 0
Create new ComboBoxTextWithEntry
Get Entry from ComboBoxTextWithEntry
set Entry's completion to EntryCompletion

]#

TODO full completion. By now it only complete first character

var iter: TreeIter val: Value

let

Create List store

gtype = gStringGetType() # init type. Work with gintro 0.7.3
listLayouts = newListStore(1, cast[pointer]( unsafeaddr gtype))

Add items to ListStore

for item in getLayouts(): discard init(val, gtype) setString(val, item) listLayouts.append(iter) listLayouts.setValue(iter, 0, val)

Create new completion

let valueCompletion = newEntryCompletion()

valueCompletion.setModel(listLayouts) valueCompletion.setTextColumn(0)

Create new ComboBox and access Entry inside it

let setKeyboadLayout = newComboBoxWithModelAndEntry(listLayouts) setKeyboadLayoutEntry = cast[Entry](setKeyboadLayout.getChild())

setKeyboadLayoutEntry.setCompletion(valueCompletion)

boxMain.add(setKeyboadLayout)

proc stop(w: Window) = mainQuit()

proc main = gtk.init() let mainBoard = newWindow() boxMain = newBox(Orientation.vertical, 3)

mainBoard.title = "Keyboard Selector"

TODO add icon here

createArea(boxMain) mainBoard.add(boxMain) mainBoard.setBorderWidth(3)

mainBoard.show() boxMain.showAll() mainBoard.connect("destroy", stop) gtk.main()

main()```

StefanSalewski commented 4 years ago

A working C code would be helpful of course.

The first error is very easy, put

discard init(val, gtype)

in front of the foor loop.

For the other error with

gtk_entry_set_text: assertion 'text != NULL' failed

I should know more about all the listview stuff. I read about it in the Krause book 12 years ago, I should read it again. Well I will see what I can do, maybe I have to ask Mr Bassi again...

StefanSalewski commented 4 years ago

You seems to be using

https://developer.gnome.org/gtk3/stable/GtkComboBox.html

that is really hard. May

https://developer.gnome.org/gtk3/stable/GtkComboBoxText.html

be sufficient for your tasks? I was going to make an example for GtkComboBoxText, but maybe will delay it to next winter, as we have really not that many GTK users currently.

For your example, I have no good idea what is wrong, I would have to look for a working C example first.

StefanSalewski commented 4 years ago

I think I have to stop for today. You linked to

https://stackoverflow.com/questions/39366248/customizing-completion-of-gtkcomboboxtext/39426800#39426800

That is really very advanced GTK use, and it is more than 3 years old, maybe it will not work with current GTK. We should better use simpler GTK examples which are based on well working official C examples.

dmknght commented 4 years ago

A working C code would be helpful of course.

The first error is very easy, put

discard init(val, gtype)

in front of the foor loop.

For the other error with

gtk_entry_set_text: assertion 'text != NULL' failed

I should know more about all the listview stuff. I read about it in the Krause book 12 years ago, I should read it again. Well I will see what I can do, maybe I have to ask Mr Bassi again...

Thank you. This fixed the first problem for me.

dmknght commented 4 years ago

You seems to be using

https://developer.gnome.org/gtk3/stable/GtkComboBox.html

that is really hard. May

https://developer.gnome.org/gtk3/stable/GtkComboBoxText.html

be sufficient for your tasks? I was going to make an example for GtkComboBoxText, but maybe will delay it to next winter, as we have really not that many GTK users currently.

For your example, I have no good idea what is wrong, I would have to look for a working C example first.

From what i understand it needs to use ListStore as its model (honestly i don't understand what the hell is it) and it needs to use the newComboBoxWithModelAndEntry(). I've tested newComboBoxTextWithEntry() and the completion works but the combo box still doesn't show the text. I'm having solution for it but it might be bad. The python code runs fine. It must be convert the value in tree list to text somehow.

dmknght commented 4 years ago

Ah so it works. My idea is appendText for the ComboBoxText() and use model for the Completion. It works but i don't really happy with the current code. I meant i have to generate 2 list of object for only 1 thing. It is acceptable for now but it is not the best optimization in my opinion. Anyway thank you for the advice. I didn't understand the flow yesterday and it took me 8 hours in mess. So in this case, the core job is we use cast to get Entry from ComboBox and use completion for Entry. It is very simple. My code:


import strutils
import re
import gintro / [gtk, gobject, glib]

proc convertLayout(data: string): string =
  #[
    Convert all weird text to "GB - English (UK)"
  ]#
  let findAllText = findAll(data, re"([a-zA-Z0-9_\/\(\),\-]+)")
  result = findAllText[0].toUpper() & " -"
  for text in findAllText[1 .. findAllText.high]:
    result &= " " & text

proc getLayouts(): seq[string] =
  #[
    Read data from path and parse ! layout section
  ]#
  const path = "/usr/share/X11/xkb/rules/xorg.lst"
  # We add a variable to check when we start parsing lines
  # After parse, we stop after ! character
  var flag = false
  for line in lines(path):
    # If the section is not ! Layout and we parsed (checked by flag)
    if line.startsWith("!") and line != "! layout" and flag == true:
      return result
    # We start parsing layout section
    elif flag == false and line == "! layout":
      flag = true
    # It should be sub sections
    else:
      # This sub section should belong to layout
      if flag == true and not isEmptyOrWhitespace(line):
        result.add(convertLayout(line))

proc createArea(boxMain: Box) =
  #[
    Steps
    https://athenajc.gitbooks.io/python-gtk-3-api/content/gtk-group/gtkentrycompletion.html
    https://stackoverflow.com/a/39426800
    Create ListStore
    Add items to ListStore == BUG == Items don't show text in ComboBoxTextWithEntry
    Create EntryCompletion
    set entrycompletion model to liststore
    set TextColumn of EntryCompletion = 0
    Create new ComboBoxTextWithEntry
    Get Entry from ComboBoxTextWithEntry
    set Entry's completion to EntryCompletion
  ]#

  # TODO full completion. By now it only complete first character

  var
    iter: TreeIter
    val: Value

  let
    # Create List store
    gtype = gStringGetType() # init type. Work with gintro 0.7.3
    listLayouts = newListStore(1, cast[pointer]( unsafeaddr gtype))
    setKeyboadLayout = newComboBoxTextWithEntry()

  discard init(val, gtype)
  # Add items to ListStore
  for item in getLayouts():
    setString(val, item)
    listLayouts.append(iter)
    listLayouts.setValue(iter, 0, val)

    setKeyboadLayout.appendText(item)

  # Create new completion
  let
    valueCompletion = newEntryCompletion()

  valueCompletion.setModel(listLayouts)
  valueCompletion.setTextColumn(0)

  # Create new ComboBox and access Entry inside it
  let
    # setKeyboadLayout = newComboBoxWithModelAndEntry(listLayouts)
    # setKeyboadLayout = newComboBoxTextWithEntry()
    setKeyboadLayoutEntry = cast[Entry](setKeyboadLayout.getChild())

  setKeyboadLayoutEntry.setCompletion(valueCompletion)

  boxMain.add(setKeyboadLayout)

proc stop(w: Window) =
  mainQuit()

proc main =
  gtk.init()
  let
    mainBoard = newWindow()
    boxMain = newBox(Orientation.vertical, 3)

  mainBoard.title = "Keyboard Selector"
  # TODO add icon here
  createArea(boxMain)
  mainBoard.add(boxMain)
  mainBoard.setBorderWidth(3)

  mainBoard.show()
  boxMain.showAll()
  mainBoard.connect("destroy", stop)
  gtk.main()

main()```
So i think this autocompletion should be in your examples because it is interesting and it is useful in real world cases. Can you please add it in your official examples?
StefanSalewski commented 4 years ago

Great that it works for you now somehow.

When the Python code works fine, then we should get it in Nim too. Working C code would be better of course.

I will investigate that python soon.

For the addition to the official examples -- well we should only add what we fully understand, and what could be easily translated to C. As C is the only code where one may get help from GTK forum.

dmknght commented 4 years ago

Great that it works for you now somehow.

When the Python code works fine, then we should get it in Nim too. Working C code would be better of course.

I will investigate that python soon.

For the addition to the official examples -- well we should only add what we fully understand, and what could be easily translated to C. As C is the only code where one may get help from GTK forum.

Thank you for your work :D I agree with you about the add when we fully understand the code. Well by now the problem is the ListStore model can work with ComboBoxWithModelAndEntry with no error. So should we close this issue or keep it open?

StefanSalewski commented 4 years ago

I think we should let this issue open for now.

Is the Python example from

https://athenajc.gitbooks.io/python-gtk-3-api/content/gtk-group/gtkentrycompletion.html

exactly what you intent?

In that case I may ask someone on GTK forum for a C version and then create a Nim version from it.

dmknght commented 4 years ago

I think we should let this issue open for now.

Is the Python example from

https://athenajc.gitbooks.io/python-gtk-3-api/content/gtk-group/gtkentrycompletion.html

exactly what you intent?

In that case I may ask someone on GTK forum for a C version and then create a Nim version from it.

The URL is the completion of the Entry object and it is easy. My problem is the Completion for the ComboBoxText so the stackoverflow URL is the example i used. As you can see, both uses ListStore for the model (which is the data) of the completion. The example in StackOverflow can display data of ListStore on ComboBoxText. Actually i don't know if C code supports it or Python did something else for easier usage. I hope you can get me because my English is not very good :p

StefanSalewski commented 4 years ago

There is some more example available which we may port to Nim.

https://gitlab.gnome.org/GNOME/gtk/-/blob/3.24.20/tests/testentrycompletion.c

The problem for me is that I do not know much about advanced listview stuff, so even when I would get something working I could be not sure if it really correct. So I would really prefer just porting a working and correct example from another language.

What we may do is starting from some available C code and ask on GTK forum how to extent it to what you want, in C. But most of the time there is no one available on that forum beside E. Bassi.

dmknght commented 4 years ago

Well in this case i think maybe gtk C doesn't support the thing i want or it is too complex so the method in my code is acceptable

StefanSalewski commented 4 years ago

Have just translated the stackoverflow python code to Nim.

I don't know if that is of some use for you, but was not that hard. Indeed I was surprised that it worked immediately after I had removed some compiler error messages. If the window is small, on my wayland screen there is some garbage left from the popup sometimes. Well maybe we should compare to the python code more carefully, but I am not sure if that python code is really fine. Have to watch TV new now, bye.

import gintro/[gtk, gobject, glib]

import strutils

type
  CompletingComboBoxText = ref object of ComboBoxText
    completionOptions: seq[string]
    populator: proc(s: string): seq[string]
    staticOptionsModel: ListStore

# we need the following two procs for now -- later we will not use that ugly cast...
proc typeTest(o: gobject.Object; s: string): bool =
  let gt = g_type_from_name(s)
  return g_type_check_instance_is_a(cast[ptr TypeInstance00](o.impl), gt).toBool

proc listStore(o: gobject.Object): gtk.ListStore =
  assert(typeTest(o, "GtkListStore"))
  cast[gtk.ListStore](o)

proc bye(w: Window) =
  mainQuit()
  echo "Bye..."

proc dynamicOptionPopulator(text: string): seq[string] =
  # Some fake returns for the populator
  const fakeDynamicOptions = [
        "_5Hf0fFKvRVa71ZPM0",
        "_8261sbF1f9ohzu2Iu",
        "_0BV96V94PJIn9si1K",
        "_0BV1sbF1f9ohzu2Iu",
        "_0BV0fFKvRVa71ZPM0",
        "_0Hf0fF4PJIn9si1Ks",
        "_6KvRVa71JIn9si1Kw",
        "_5HKvRVa71Va71ZPM0",
        "_8261sbF1KvRVa71ZP",
        "_0BKvRVa71JIn9si1K",
        "_0BV1KvRVa71ZPu2Iu",
        "_0BV0fKvRVa71ZZPM0",
        "_0Hf0fF4PJIbF1f9oh",
        "_61sbFV0fFKn9si1Kw",
        "_5Hf0fFKvRVa71ozu2",
  ]
  for fakeDynamicOption in fakeDynamicOptions:
    if fakeDynamicOption.startswith(text):
      result.add(fakeDynamicOption)

proc updateCompletion(entry: Entry; self: CompletingComboBoxText) =
  var
    iter: TreeIter
    val: Value
  # Get the current content of the entry
  let text = entry.getText()

  # Get the completion which needs to be updated
  let completion = entry.getCompletion()

  if text.startsWith("_") and len(text) >= completion.getMinimumKeyLength():
    # Fetch the options from the populator for a given text
    self.completionOptions = self.populator(text)

    # Create a temporary model for the completion and fill it
    let gtype = typeFromName("gchararray")
    let dynamicModel = newListStore(1, cast[pointer]( unsafeaddr gtype))

    discard init(val, gtype)
    for completionOption in self.completionOptions:
      setString(val, completionOption)
      dynamicModel.append(iter)
      dynamicModel.setValue(iter, 0, val)
    completion.setModel(dynamicModel)
  else:
    # Restore the default static options
    completion.setModel(self.staticOptionsModel)

proc newCompletingComboBoxText(staticOptions: openArray[string], populator: proc(s: string): seq[string]): CompletingComboBoxText =
  var self = newComboBoxTextWithEntry(CompletingComboBoxText)
  self.populator = populator
  let completion = gtk.newEntryCompletion()
  completion.setInlineCompletion
  completion.setTextColumn(0)
  completion.setMinimumKeyLength(2)

  # Set the completion model to the combobox model such that we can also autocomplete these options
  self.staticOptionsModel = listStore(self.getModel)
  completion.setModel(self.staticOptionsModel)

  # The child of the combobox is the entry if 'has_entry' was set to True
  let entry = cast[Entry](self.getChild())
  entry.setCompletion(completion)

  # Set the active option of the combobox to 0 (which is an empty field)
  self.setActive(0)

  # Fill the model with the static options (could also be used for a history or something)
  for option in staticOptions:
    self.appendText(option)

  # Connect a listener to adjust the model when the user types something
  entry.connect("changed", updateCompletion, self)
  return self

proc demo() =
  gtk.init()
  let window = gtk.newWindow()
  window.connect("destroy", bye)
  # Add some static options
  const fakeStaticOptions = [
        "comment",
        "if",
        "the_GUI",
        "the_system",
        "payload_json",
        "x1",
        "payload_json",
        "payload_vectval"
  ]

  # Add the the Combobox
  let ccb = newCompletingComboBoxText(fakeStaticOptions, dynamicOptionPopulator)
  window.add(ccb)

  # Show it
  window.showAll()
  gtk.main()

when isMainModule:
  demo()
dmknght commented 4 years ago

Have just translated the stackoverflow python code to Nim.

I don't know if that is of some use for you, but was not that hard. Indeed I was surprised that it worked immediately after I had removed some compiler error messages. If the window is small, on my wayland screen there is some garbage left from the popup sometimes. Well maybe we should compare to the python code more carefully, but I am not sure if that python code is really fine. Have to watch TV new now, bye.

import gintro/[gtk, gobject, glib]

import strutils

type
  CompletingComboBoxText = ref object of ComboBoxText
    completionOptions: seq[string]
    populator: proc(s: string): seq[string]
    staticOptionsModel: ListStore

# we need the following two procs for now -- later we will not use that ugly cast...
proc typeTest(o: gobject.Object; s: string): bool =
  let gt = g_type_from_name(s)
  return g_type_check_instance_is_a(cast[ptr TypeInstance00](o.impl), gt).toBool

proc listStore(o: gobject.Object): gtk.ListStore =
  assert(typeTest(o, "GtkListStore"))
  cast[gtk.ListStore](o)

proc bye(w: Window) =
  mainQuit()
  echo "Bye..."

proc dynamicOptionPopulator(text: string): seq[string] =
  # Some fake returns for the populator
  const fakeDynamicOptions = [
        "_5Hf0fFKvRVa71ZPM0",
        "_8261sbF1f9ohzu2Iu",
        "_0BV96V94PJIn9si1K",
        "_0BV1sbF1f9ohzu2Iu",
        "_0BV0fFKvRVa71ZPM0",
        "_0Hf0fF4PJIn9si1Ks",
        "_6KvRVa71JIn9si1Kw",
        "_5HKvRVa71Va71ZPM0",
        "_8261sbF1KvRVa71ZP",
        "_0BKvRVa71JIn9si1K",
        "_0BV1KvRVa71ZPu2Iu",
        "_0BV0fKvRVa71ZZPM0",
        "_0Hf0fF4PJIbF1f9oh",
        "_61sbFV0fFKn9si1Kw",
        "_5Hf0fFKvRVa71ozu2",
  ]
  for fakeDynamicOption in fakeDynamicOptions:
    if fakeDynamicOption.startswith(text):
      result.add(fakeDynamicOption)

proc updateCompletion(entry: Entry; self: CompletingComboBoxText) =
  var
    iter: TreeIter
    val: Value
  # Get the current content of the entry
  let text = entry.getText()

  # Get the completion which needs to be updated
  let completion = entry.getCompletion()

  if text.startsWith("_") and len(text) >= completion.getMinimumKeyLength():
    # Fetch the options from the populator for a given text
    self.completionOptions = self.populator(text)

    # Create a temporary model for the completion and fill it
    let gtype = typeFromName("gchararray")
    let dynamicModel = newListStore(1, cast[pointer]( unsafeaddr gtype))

    discard init(val, gtype)
    for completionOption in self.completionOptions:
      setString(val, completionOption)
      dynamicModel.append(iter)
      dynamicModel.setValue(iter, 0, val)
    completion.setModel(dynamicModel)
  else:
    # Restore the default static options
    completion.setModel(self.staticOptionsModel)

proc newCompletingComboBoxText(staticOptions: openArray[string], populator: proc(s: string): seq[string]): CompletingComboBoxText =
  var self = newComboBoxTextWithEntry(CompletingComboBoxText)
  self.populator = populator
  let completion = gtk.newEntryCompletion()
  completion.setInlineCompletion
  completion.setTextColumn(0)
  completion.setMinimumKeyLength(2)

  # Set the completion model to the combobox model such that we can also autocomplete these options
  self.staticOptionsModel = listStore(self.getModel)
  completion.setModel(self.staticOptionsModel)

  # The child of the combobox is the entry if 'has_entry' was set to True
  let entry = cast[Entry](self.getChild())
  entry.setCompletion(completion)

  # Set the active option of the combobox to 0 (which is an empty field)
  self.setActive(0)

  # Fill the model with the static options (could also be used for a history or something)
  for option in staticOptions:
    self.appendText(option)

  # Connect a listener to adjust the model when the user types something
  entry.connect("changed", updateCompletion, self)
  return self

proc demo() =
  gtk.init()
  let window = gtk.newWindow()
  window.connect("destroy", bye)
  # Add some static options
  const fakeStaticOptions = [
        "comment",
        "if",
        "the_GUI",
        "the_system",
        "payload_json",
        "x1",
        "payload_json",
        "payload_vectval"
  ]

  # Add the the Combobox
  let ccb = newCompletingComboBoxText(fakeStaticOptions, dynamicOptionPopulator)
  window.add(ccb)

  # Show it
  window.showAll()
  gtk.main()

when isMainModule:
  demo()

Thank you very much. From your code i can see my method is the same:

  1. Add items to ListStore. Use it as Model of Completion
  2. Add items to ComboBox by using appendText()
  3. "Access" ComboBox's Entry by using cast The python script is having an extra example that programmers can customize the values of completion. The values are not in combobox. So i guess we can close this issue now?
StefanSalewski commented 4 years ago

Have done a short test with the original Python code run with python2. It has the same problem, the popup box generates garbage on the screen when the box is larger than the window. Maybe a wayland or gtk issue, but I think not a Nim/gintro one. I think we can let this issue open for a few weeks, it has now some useful Nim code. I intent to close a lot of the issues soon, will start with the old once.

dmknght commented 4 years ago

Well imo it can be the problem of C code. In this case the text isn't there but all lines (rows) are still there. If you like to add this completion as examples, i can help you write 2 examples with the entry and combobox :D