Get number of lines in EditorGadget (cross-platform)

Share your advanced PureBasic knowledge/code with the community.
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Get number of lines in EditorGadget (cross-platform)

Post by Shardik »

The following example demonstrates how to get the number of lines currently contained in an EditorGadget (works also with flag #PB_Editor_WordWrap set and counts even the lines in a currently invisible part of the EditorGadget which need moving the scroll bar to become visible). In Windows it's quite easy by simply using

Code: Select all

CountGadgetItems(EditorGadgetID)
But in Linux and MacOS it's much more difficult needing a bunch of API functions, so that I have put together the following true cross-platform example:

Code: Select all

EnableExplicit

#Text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " +
  "nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, " +
  "sed diam voluptua. At vero eos et accusam et justo duo dolores et ea " +
  "rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum " +
  "dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " +
  "sed diam nonumy eirmod tempor invidunt ut labor."

CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
  Procedure.I GetLineHeight(EditorGadgetID.I)
    Protected Dictionary.I
    Protected Font.I
    Protected i.I
    Protected KeyArray.I
    Protected KeyName.I
    Protected LineHeight.CGFloat
    
    Dictionary = CocoaMessage(0, GadgetID(EditorGadgetID), "typingAttributes")
    KeyArray = CocoaMessage(0, Dictionary, "allKeys")
    
    For i = 0 To CocoaMessage(0, KeyArray, "count") - 1
      KeyName = CocoaMessage(0, KeyArray, "objectAtIndex:", i)
      
      If PeekS(CocoaMessage(0, KeyName, "UTF8String"), -1, #PB_UTF8) = "NSFont"
        Font = CocoaMessage(0, Dictionary, "objectForKey:", KeyName)
        
        If Font
          CocoaMessage(@LineHeight, CocoaMessage(0, GadgetID(EditorGadgetID),
            "layoutManager"), "defaultLineHeightForFont:", Font)
        EndIf
        
        Break
      EndIf
    Next i
    
    ProcedureReturn Int(LineHeight)
  EndProcedure
CompilerEndIf

Procedure.I GetLineCount(WindowID.I, EditorGadgetID.I)
  Protected LineCount.I
  
  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Linux
      Protected LineHeight.I
      Protected TextBlockHeight.I
      Protected TextIter.GtkTextIter
      Protected TextView.I = GadgetID(EditorGadgetID)
      Protected y.I

      ; ----- Get height of a single text line for font used in EditorGadget
      If StartDrawing(WindowOutput(WindowID))
        DrawingFont(GetGadgetFont(EditorGadgetID))
        LineHeight = TextHeight("Test")
        StopDrawing()
      EndIf
      
      ; ----- Get height of complete wrapped text block in EditorGadget
      gtk_text_view_get_line_at_y_(TextView, @TextIter, GadgetY(EditorGadgetID), 0)
      gtk_text_view_get_line_yrange_(TextView, @TextIter, @y, @TextBlockHeight)
      
      ; ----- Calculate number of lines (also invisible ones)
      LineCount = TextBlockHeight / LineHeight            
    CompilerCase #PB_OS_MacOS
      Protected LineHeight.I
      Protected Rect.NSRect
      Protected TextBlockHeight.I

      ; ----- Get height of complete wrapped text block in EditorGadget
      CocoaMessage(@Rect, CocoaMessage(0, GadgetID(EditorGadgetID),
        "layoutManager"), "usedRectForTextContainer:", CocoaMessage(0,
        GadgetID(EditorGadgetID), "textContainer"))
      ; ----- Calculate number of lines (also invisible ones)
      LineCount = Int(Rect\size\height) / GetLineHeight(EditorGadgetID)
    CompilerCase #PB_OS_Windows
      LineCount = CountGadgetItems(EditorGadgetID)
  CompilerEndSelect

  ProcedureReturn LineCount
EndProcedure

OpenWindow(0, 200, 100, 300, 400, "EditorGadget")
EditorGadget(0, 5, 5, WindowWidth(0) - 10, WindowHeight(0) - 10,
  #PB_Editor_WordWrap)

; ----- Workaround for Linux bug in flag #PB_Editor_WordWrap
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
  gtk_text_view_set_wrap_mode_(GadgetID(0), #GTK_WRAP_WORD)
CompilerEndIf

SetGadgetFont(0, LoadFont(0, "Arial", 15))
SetGadgetText(0, #Text)

; ----- Wait until text is displayed (Necessary on Linux and MacOS!)
While WindowEvent() : Wend

MessageRequester("Info", "Number of lines in EditorGadget: " + GetLineCount(0, 0))

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
Last edited by Shardik on Mon Dec 22, 2014 11:00 am, edited 1 time in total.
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Get number of lines in EditorGadget (cross-platform)

Post by infratec »

Thank you very much :!:

But shouldn't this posted somewhere in the bug section :?:
Than Fred notice this 100% and can include it in one of the next versions.

Bernd
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Get number of lines in EditorGadget (cross-platform)

Post by Shardik »

infratec wrote:But shouldn't this posted somewhere in the bug section :?:
Than Fred notice this 100% and can include it in one of the next versions.
The above code was developed as a workaround for this Linux bug report reported by Kukulkan... :wink:
User avatar
Mindphazer
Enthusiast
Enthusiast
Posts: 456
Joined: Mon Sep 10, 2012 10:41 am
Location: Savoie

Re: Get number of lines in EditorGadget (cross-platform)

Post by Mindphazer »

I think this :

Code: Select all

; ----- Workaround for Linux bug in flag #PB_Editor_WordWrap
CompilerIf #PB_OS_Linux
  gtk_text_view_set_wrap_mode_(GadgetID(0), #GTK_WRAP_WORD)
CompilerEndIf
should be :

Code: Select all

; ----- Workaround for Linux bug in flag #PB_Editor_WordWrap
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
  gtk_text_view_set_wrap_mode_(GadgetID(0), #GTK_WRAP_WORD)
CompilerEndIf
;-)
MacBook Pro 16" M4 Pro - 24 Gb - MacOS 15.4.1 - Iphone 15 Pro Max - iPad at home
...and unfortunately... Windows at work...
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Get number of lines in EditorGadget (cross-platform)

Post by Shardik »

Thank you Mindphazer for pinpointing the error in the CompilerIf statement. I have corrected my above example code.
BarryG
Addict
Addict
Posts: 4123
Joined: Thu Apr 18, 2019 8:17 am

Re: Get number of lines in EditorGadget (cross-platform)

Post by BarryG »

Is there a way to know the number of VISIBLE lines that an EditorGadget has? Not how many lines of actual text is in it. Thanks!
Axolotl
Addict
Addict
Posts: 802
Joined: Wed Dec 31, 2008 3:36 pm

Re: Get number of lines in EditorGadget (cross-platform)

Post by Axolotl »

Hi, maybe this can help..... (on windows only)

Code: Select all

CompilerIf #PB_Compiler_OS = #PB_OS_Windows 
Procedure CountVisbibleLines(Gadget)  ; INT 
  Protected ret_line, char, pt.POINT, rc.RECT 
  
  SendMessage_(GadgetID(Gadget), #EM_GETRECT, 0, @rc)  ; get the client area 
  pt\x = 0
  pt\y = rc\bottom 
  char = SendMessage_(GadgetID(Gadget), #EM_CHARFROMPOS, 0, @pt)
  ret_line = SendMessage_(GadgetID(Gadget), #EM_EXLINEFROMCHAR, 0, char) 
  ProcedureReturn ret_line 
EndProcedure 
CompilerElse 
Debug "Sorry, no idea"
CompilerEndIf 

CompilerIf #PB_Compiler_IsMainFile 
  If OpenWindow(0, 0, 0, 322, 150, "EditorGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    EditorGadget(0, 8, 8, 306, 133)
    For a = 0 To 10
      AddGadgetItem(0, a, "Line "+Str(a))
    Next 
    Debug CountVisbibleLines(0) 
    Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
  EndIf
CompilerEndIf

Just because it worked doesn't mean it works.
PureBasic 6.04 (x86) and <latest stable version and current alpha/beta> (x64) on Windows 11 Home. Now started with Linux (VM: Ubuntu 22.04).
BarryG
Addict
Addict
Posts: 4123
Joined: Thu Apr 18, 2019 8:17 am

Re: Get number of lines in EditorGadget (cross-platform)

Post by BarryG »

Works for the most part, Axolotl (thanks), but it doesn't work if you directly set the text. See:

Code: Select all

Procedure CountVisbibleLines(Gadget)
  Protected ret_line, char, pt.POINT, rc.RECT 
  SendMessage_(GadgetID(Gadget), #EM_GETRECT, 0, @rc)
  pt\x = 0
  pt\y = rc\bottom 
  char = SendMessage_(GadgetID(Gadget), #EM_CHARFROMPOS, 0, @pt)
  ret_line = SendMessage_(GadgetID(Gadget), #EM_EXLINEFROMCHAR, 0, char) 
  ProcedureReturn ret_line + 1
EndProcedure 

If OpenWindow(0, 0, 0, 322, 100, "EditorGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  EditorGadget(0, 8, 8, 306, 85)
  SetGadgetText(0, "Should debug 5"+#LF$+"2"+#LF$+"3")
  Debug CountVisbibleLines(0) 
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
Axolotl
Addict
Addict
Posts: 802
Joined: Wed Dec 31, 2008 3:36 pm

Re: Get number of lines in EditorGadget (cross-platform)

Post by Axolotl »

Code: Select all

CompilerIf #PB_Compiler_OS = #PB_OS_Windows 
Procedure CountVisbibleLines(Gadget)  ; INT 
  Protected index, first_line, count, pt.POINT, rc.RECT 

  SendMessage_(GadgetID(Gadget), #EM_GETRECT, 0, @rc)  ; get the client area 
  first_line = SendMessage_(GadgetID(Gadget), #EM_GETFIRSTVISIBLELINE, 0, 0) 
  count = SendMessage_(GadgetID(Gadget), #EM_GETLINECOUNT, 0, 0)  

  For index = first_line To count 
    SendMessage_(GadgetID(Gadget), #EM_POSFROMCHAR, @pt, SendMessage_(GadgetID(Gadget), #EM_LINEINDEX, index, 0)) 

    If pt\y >= (rc\bottom - rc\top) 
      Break 
    EndIf 
  Next index 
  ProcedureReturn index - 1 
EndProcedure 
CompilerElse 
Debug "Sorry, no idea"
CompilerEndIf 


CompilerIf #PB_Compiler_IsMainFile 
  If OpenWindow(0, 0, 0, 322, 150, "EditorGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    EditorGadget(0, 8, 8, 306, 133)
    For a = 1 To 5
      AddGadgetItem(0, a, "Line "+Str(a))
    Next 
;   SetGadgetText(0, "Should debug 5"+#CRLF$+"2"+#CRLF$+"3"+#CRLF$)
    SetGadgetText(0, "1"+#LF$)
  ; SetGadgetText(0, "1"+#LF$+"2")
  
    Debug CountVisbibleLines(0) 
    Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
  EndIf
CompilerEndIf
Just because it worked doesn't mean it works.
PureBasic 6.04 (x86) and <latest stable version and current alpha/beta> (x64) on Windows 11 Home. Now started with Linux (VM: Ubuntu 22.04).
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Get number of lines in EditorGadget (cross-platform)

Post by Shardik »

I have chosen a different approach for Windows than the more complicated one Axolotl has chosen. The Microsoft API offers the message LVM_GETCOUNTPERPAGE to query the lines currently visible:
Calculates the number of items that can fit vertically in the visible area of a list-view control when in list or report view. Only fully visible items are counted.
I have also added the cross-platform parts for Linux and MacOS. In my example the number of currently visible lines are displayed in the title bar. By increasing or decreasing the height of the window (and the embedded ListIconGadget) with your mouse the line count is dynamically updated in the window title bar.

But there are some cross-plattorm shortcomings you should be aware of because the API in Linux, MacOS and Windows is very different:
  • In Windows only fully visible lines are counted but in Linux and MacOS also partly visible lines.
  • In Windows also empty lines are counted but in Linux and MacOS only lines with content.
Please report also other behavioural differences between the different operating systems if you find some!

Code: Select all

EnableExplicit

CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Linux
    ImportC ""
      gtk_tree_view_get_visible_range(*TreeView.GtkTreeView, *StartPath,
        *EndPath)
    EndImport
    
    Procedure.F GetVisibleRowCount(ListIconID.I)
      Protected *EndPath
      Protected FirstVisibleRow.I
      Protected LastVisibleRow.I
      Protected *StartPath
      Protected VisibleRowCount.I
      
      If gtk_tree_view_get_visible_range(GadgetID(ListIconID),
        @*StartPath, @*EndPath)
        FirstVisibleRow = PeekL(gtk_tree_path_get_indices_(*StartPath))
        LastVisibleRow = PeekL(gtk_tree_path_get_indices_(*EndPath))
        VisibleRowCount = LastVisibleRow - FirstVisibleRow + 1
      EndIf
      
      ProcedureReturn VisibleRowCount
    EndProcedure
  CompilerCase #PB_OS_MacOS
    Procedure.I GetVisibleRowCount(ListIconID.I)
      Protected ContentView.I
      Protected EnclosingScrollView.I
      Protected VisibleRange.NSRange
      Protected VisibleRect.NSRect
      
      ; ----- Get scroll view inside of ListIconGadget
      EnclosingScrollView = CocoaMessage(0, GadgetID(ListIconID),
        "enclosingScrollView")
      
      If EnclosingScrollView
        ContentView = CocoaMessage(0, EnclosingScrollView, "contentView")
        ; ----- Get visible area
        ;       (automatically subtract horizontal scrollbar if shown)
        CocoaMessage(@VisibleRect, ContentView, "documentVisibleRect")
        ; ----- Subtract border width
        If CocoaMessage(0, EnclosingScrollView, "borderType") > 0
          VisibleRect\size\height - 5
        EndIf

        ; ----- Get number of rows visible
        CocoaMessage(@VisibleRange, GadgetID(ListIconID),
          "rowsInRect:@", @VisibleRect)
        ProcedureReturn Int(VisibleRange\length)
      EndIf
    EndProcedure
  CompilerCase #PB_OS_Windows
    Procedure.I GetVisibleRowCount(ListIconID.I)
      ProcedureReturn SendMessage_(GadgetID(ListIconID), #LVM_GETCOUNTPERPAGE,
        0, 0)
  EndProcedure
CompilerEndSelect

Define i.I
Define VisibleRowCount.I

OpenWindow(0, 100, 100, 280, 131, "",
  #PB_Window_SystemMenu | #PB_Window_SizeGadget)
WindowBounds(0, WindowWidth(0), 90, #PB_Ignore, #PB_Ignore)
ListIconGadget(0, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 20,
  "Column 1", 115, #PB_ListIcon_GridLines)
AddGadgetColumn(0, 1, "Column 2", 115)

For i = 1 To 10
  AddGadgetItem(0, -1, "Row " + Str(i) + ", Column 1" + #LF$ +
    "Row " + Str(i) + ", Column 2")
Next i

CompilerIf #PB_Compiler_OS = #PB_OS_Linux
  Delay(10)
  While WindowEvent() : Wend
CompilerEndIf

SetWindowTitle(0, "Visible rows: " + GetVisibleRowCount(0))

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
    Case #PB_Event_SizeWindow
      VisibleRowCount = GetVisibleRowCount(0)
      ResizeGadget(0, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 20)
      SetWindowTitle(0, "Visible rows: " + VisibleRowCount)
  EndSelect
ForEver
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4946
Joined: Sun Apr 12, 2009 6:27 am

Re: Get number of lines in EditorGadget (cross-platform)

Post by RASHAD »

For Windows

Code: Select all

LoadFont(0,"Tahoma",12)

Procedure sizeCB()
  ResizeGadget(0,8,8,WindowWidth(0)-16,WindowHeight(0)-16)
  SendMessage_(GadgetID(0),#EM_SETSEL, 0, 0)
  index = SendMessage_(GadgetID(0),#EM_LINEINDEX,1,0)
  SendMessage_(GadgetID(0),#EM_SETSEL,index,index)
  SetActiveGadget(0)
  GetCaretPos_(p.POINT)
  Debug GadgetHeight(0)/p\y
  SetActiveGadget(-1)
EndProcedure  

If OpenWindow(0, 0, 0, 322, 150, "EditorGadget", #PB_Window_SystemMenu | #PB_Window_SizeGadget |#PB_Window_ScreenCentered)
  EditorGadget(0, 8, 8, 306, 133)
  SetGadgetFont(0,FontID(0))
  For a = 0 To 50
    AddGadgetItem(0, a, "Line "+Str(a))
  Next
  index = SendMessage_(GadgetID(0),#EM_LINEINDEX,1,0)
  SendMessage_(GadgetID(0),#EM_SETSEL,index,index)
  SetActiveGadget(0)
  GetCaretPos_(p.POINT)
  Debug GadgetHeight(0)/p\y
  SetActiveGadget(-1)
  
  BindEvent(#PB_Event_SizeWindow,@sizeCB())
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
Egypt my love
Post Reply