ListIconGadget: No #PB_EventType_Change!

Mac OSX specific forum
Lebostein
Addict
Addict
Posts: 807
Joined: Fri Jun 11, 2004 7:07 am

ListIconGadget: No #PB_EventType_Change!

Post by Lebostein »

If you change the selected row in a ListIconGadget with the arrow keys, no #PB_EventType_Change Event is triggered! No chance to find out if the selection inside the ListIconGadget was changed....

This is Mac OS only. With Windows it works.
Lebostein
Addict
Addict
Posts: 807
Joined: Fri Jun 11, 2004 7:07 am

Re: ListIconGadget: No #PB_EventType_Change!

Post by Lebostein »

It seems the bug is active only, if a GadgetCallback is activated:
http://www.purebasic.fr/english/viewtop ... 53#p490253
Fred
Administrator
Administrator
Posts: 16617
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: ListIconGadget: No #PB_EventType_Change!

Post by Fred »

Could you post a full working snippet please ?
Lebostein
Addict
Addict
Posts: 807
Joined: Fri Jun 11, 2004 7:07 am

Re: ListIconGadget: No #PB_EventType_Change!

Post by Lebostein »

Left gadget (with a callback) don't send a #PB_EventType_Change if you select a new row with up/down arrow keys:

Code: Select all

EnableExplicit

Structure CallbackEntry
  WindowID.I
  ListIconID.I
  DefaultCallback.I
EndStructure

NewList CallbackEntry.CallbackEntry()

CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Linux ; ------------------------------------------------
    ProcedureC ColumnHeaderClickCallback(*Column, ListIconData.I)
      Shared CallbackEntry()

      ForEach CallbackEntry()
        If ListIconData >> 16 = CallbackEntry()\ListIconID
          Break
        EndIf
      Next

      PostEvent(#PB_Event_Gadget, CallbackEntry()\WindowID,
        CallbackEntry()\ListIconID, #PB_EventType_LeftClick,
        (ListIconData & $FFFF) + 1)
    EndProcedure

    Procedure SetGadgetCallback(WindowID.I, ListIconID.I)
      Shared CallbackEntry()

      Protected Column.I
      Protected ColumnCount.I
      Protected ColumnIndex.I
      Protected *ListStore.GtkListStore

      AddElement(CallbackEntry())
      CallbackEntry()\WindowID = WindowID
      CallbackEntry()\ListIconID = ListIconID
      gtk_tree_view_set_headers_clickable_(GadgetID(ListIconID), #True)
      *ListStore = gtk_tree_view_get_model_(GadgetID(ListIconID))
      ColumnCount = (*ListStore\n_columns - 3) / 3

      For ColumnIndex = 0 To ColumnCount - 1
        Column = gtk_tree_view_get_column_(GadgetID(CallbackEntry()\ListIconID),
          ColumnIndex)

        If Column
          g_signal_connect_data_(Column, "clicked",
            @ColumnHeaderClickCallback(), ListIconID << 16 | ColumnIndex, 0, 0)
        EndIf
      Next ColumnIndex
    EndProcedure
  CompilerCase #PB_OS_MacOS ; ------------------------------------------------
    ImportC ""
      sel_registerName(MethodName.S)
      class_addMethod(Class.I, Selector.I, Implementation.I, Types.S)
    EndImport

    Procedure.S ConvertToUTF8(String.S)
      Protected UTF8String.S = Space(StringByteLength(String))
      PokeS(@UTF8String, String, -1, #PB_UTF8)
      ProcedureReturn UTF8String
    EndProcedure
   
    ProcedureC ColumnHeaderClickCallback(Object.I, Selector.I, TableView.I,
      TableColumn.I)
      Shared CallbackEntry()

      Protected ClickedHeaderColumn.I
 
      ForEach CallbackEntry()
        If TableView = GadgetID(CallbackEntry()\ListIconID)
          Break
        EndIf
      Next
   
      ClickedHeaderColumn = Val(PeekS(CocoaMessage(0,
        CocoaMessage(0, TableColumn, "identifier"),
        "UTF8String"), -1, #PB_UTF8))
      PostEvent(#PB_Event_Gadget, CallbackEntry()\WindowID,
        CallbackEntry()\ListIconID, #PB_EventType_LeftClick,
        ClickedHeaderColumn + 1)
    EndProcedure
   
    Procedure SetGadgetCallback(WindowID.I, ListIconID.I)
      Shared CallbackEntry()
     
      Protected AppDelegate.I
      Protected DelegateClass.I
      Protected Selector.I = sel_registerName(ConvertToUTF8("tableView:didClickTableColumn:"))
      Protected Types.S = ConvertToUTF8("v@:@@")

      AddElement(CallbackEntry())
      CallbackEntry()\WindowID = WindowID
      CallbackEntry()\ListIconID = ListIconID
      AppDelegate = CocoaMessage(0,
        CocoaMessage(0, 0, "NSApplication sharedApplication"), "delegate")
      DelegateClass = CocoaMessage(0, AppDelegate, "class")
      class_addMethod(DelegateClass, Selector, @ColumnHeaderClickCallback(),
        Types)
      CocoaMessage(0, GadgetID(CallbackEntry()\ListIconID),
        "setDelegate:", AppDelegate)
    EndProcedure
  CompilerCase #PB_OS_Windows ; ----------------------------------------------
    Procedure ColumnHeaderClickCallback(WindowHandle.I, Msg.I, WParam.I,
      LParam.I)
      Shared CallbackEntry()

      Protected Result.I
      Protected *Header.HD_NOTIFY

      ForEach CallbackEntry()
        If WindowHandle = GadgetID(CallbackEntry()\ListIconID)
          Break
        EndIf
      Next

      Result = CallWindowProc_(CallbackEntry()\DefaultCallback, WindowHandle,
        Msg, WParam, LParam)

      If Msg = #WM_NOTIFY
        *Header = LParam

        If *Header\hdr\code = #HDN_ITEMCLICK
          PostEvent(#PB_Event_Gadget, CallbackEntry()\WindowID,
            CallbackEntry()\ListIconID, #PB_EventType_LeftClick,
            *Header\iItem + 1)
        EndIf
      EndIf

      ProcedureReturn Result
    EndProcedure

    Procedure SetGadgetCallback(WindowID.I, ListIconID.I)
      Shared CallbackEntry()

      AddElement(CallbackEntry())
      CallbackEntry()\WindowID = WindowID
      CallbackEntry()\ListIconID = ListIconID
      CallbackEntry()\DefaultCallback = SetWindowLongPtr_(GadgetID(CallbackEntry()\ListIconID),
        #GWL_WNDPROC, @ColumnHeaderClickCallback())
    EndProcedure ; -----------------------------------------------------------
CompilerEndSelect

; ========================================== SPECIFIC CODE FOR TESTING

Enumeration
  #zero
  #windows_nb
  #list1_nb
  #list2_nb
EndEnumeration

#Title_column1$ = "Name"
#Title_column2$ = "Address"

Define GadgetID.I

OpenWindow(#windows_nb, 0, 0, 950, 150, "Detect left click on header cell",#PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ListIconGadget(#list1_nb, 10, 10, 430, WindowHeight(#windows_nb) - 20, #Title_column1$,
  110, #PB_ListIcon_FullRowSelect)
AddGadgetColumn(#list1_nb, 1, #Title_column2$, 300)
AddGadgetItem(#list1_nb, -1, "Harry Rannit" + #LF$ +
  "12 Parliament Way, Battle Street, By the Bay")
AddGadgetItem(#list1_nb, -1, "Ginger Brokeit"+ #LF$ +
  "130 PureBasic Road, BigTown, CodeCity")
AddGadgetItem(#list1_nb, -1, "Didi Foundit"+ #LF$ +
  "321 Logo Drive, Mouse House, Downtown")

ListIconGadget(#list2_nb, 460, 10, 430, WindowHeight(#windows_nb) - 20, #Title_column1$,
  110, #PB_ListIcon_FullRowSelect)
AddGadgetColumn(#list2_nb, 1, #Title_column2$, 300)
AddGadgetItem(#list2_nb, -1, "Harry Rannit" + #LF$ +
  "12 Parliament Way, Battle Street, By the Bay")
AddGadgetItem(#list2_nb, -1, "Ginger Brokeit"+ #LF$ +
  "130 PureBasic Road, BigTown, CodeCity")
AddGadgetItem(#list2_nb, -1, "Didi Foundit"+ #LF$ +
  "321 Logo Drive, Mouse House, Downtown")

SetGadgetCallback(#windows_nb, #list1_nb)
;SetGadgetCallback(#windows_nb, #list2_nb) ; -> gadget 2 without that callback

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      CompilerIf #PB_Compiler_OS = #PB_OS_Windows
        ForEach CallbackEntry()
          SetWindowLongPtr_(GadgetID(CallbackEntry()\ListIconID),
            #GWL_WNDPROC, CallbackEntry()\DefaultCallback)
        Next
      CompilerEndIf

      Break
    Case #PB_Event_Gadget
      GadgetID = EventGadget()

      Select GadgetID
        Case #list1_nb, #list2_nb
          Select EventType()
          Case #PB_EventType_LeftClick
            If EventData()
              Debug "Left click on header of column " + Str(EventData() - 1) + " gadget=" + Str(GadgetID)
            Else
              Debug "Left click on row " + Str(GetGadgetState(GadgetID)) + " gadget=" + Str(GadgetID)
            EndIf
          Case #PB_EventType_Change
            Debug "changed"
          EndSelect
      EndSelect
  EndSelect
ForEver
Fred
Administrator
Administrator
Posts: 16617
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: ListIconGadget: No #PB_EventType_Change!

Post by Fred »

You are replacing the delegate, so PB one isn't called anymore. It's not a PB bug.
Lebostein
Addict
Addict
Posts: 807
Joined: Fri Jun 11, 2004 7:07 am

Re: ListIconGadget: No #PB_EventType_Change!

Post by Lebostein »

Fred wrote:You are replacing the delegate, so PB one isn't called anymore. It's not a PB bug.
I don't know what you mean. I copied this code from the forum.
Is it possible to catch a click on the List Icon Gadget header without "replacing the delegate"?
Lebostein
Addict
Addict
Posts: 807
Joined: Fri Jun 11, 2004 7:07 am

Re: ListIconGadget: No #PB_EventType_Change!

Post by Lebostein »

Can you add this "header click" official to PureBasic? Then we don't need to "replace the delegate".
That would be great.
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: ListIconGadget: No #PB_EventType_Change!

Post by Shardik »

I have taken Lebostein's example, stripped off all specific Linux and Windows code for a clearer outline (so now it's only runnable on MacOS) and implemented a second callback for the method tableViewSelectionDidChange: to catch and signal all selection changes in both of the two ListIconGadgets.

I have tested the example successfully on MacOS 10.6.8 (Snow Leopard) and MacOS 10.11.6 (El Capitan) with PB 5.43 x86 and x64 (ASCII and Unicode mode) and with PB 5.50 x86 and x64.

Code: Select all

EnableExplicit

ImportC ""
  sel_registerName(MethodName.P-ASCII)
  class_addMethod(Class.I, Selector.I, Implementation.I, Types.P-ASCII)
EndImport

Structure CallbackEntry
  WindowID.I
  ListIconID.I
  DefaultCallback.I
EndStructure

Define AppDelegate.I = CocoaMessage(0,
  CocoaMessage(0, 0, "NSApplication sharedApplication"), "delegate")
Define DelegateClass.I = CocoaMessage(0, AppDelegate, "class")
Define NotificationCenter.I = CocoaMessage(0, 0,
  "NSNotificationCenter defaultCenter")

NewList CallbackEntry.CallbackEntry()

ProcedureC ColumnHeaderClickCallback(Object.I, Selector.I, TableView.I,
  TableColumn.I)
  Shared CallbackEntry()
  
  Protected ClickedHeaderColumn.I
  
  ForEach CallbackEntry()
    If TableView = GadgetID(CallbackEntry()\ListIconID)
      Break
    EndIf
  Next
  
  ClickedHeaderColumn = Val(PeekS(CocoaMessage(0,
    CocoaMessage(0, TableColumn, "identifier"),
    "UTF8String"), -1, #PB_UTF8))
  PostEvent(#PB_Event_Gadget, CallbackEntry()\WindowID,
    CallbackEntry()\ListIconID, #PB_EventType_LeftClick,
    ClickedHeaderColumn + 1)
EndProcedure

ProcedureC SelectionDidChangeCallback(Object.I, Selector.I, Notification.I)
  Shared CallbackEntry()
  Static ChangeSignalled.I

  If ChangeSignalled
    PostEvent(#PB_Event_Gadget, CallbackEntry()\WindowID,
      CallbackEntry()\ListIconID, #PB_EventType_Change)
    ChangeSignalled = #False
  Else
    ChangeSignalled = #True
  EndIf
EndProcedure

Procedure SetGadgetCallback(WindowID.I, ListIconID.I)
  Shared AppDelegate.I
  Shared CallbackEntry()
  Shared DelegateClass.I
  Shared NotificationCenter.I
  
  AddElement(CallbackEntry())
  CallbackEntry()\WindowID = WindowID
  CallbackEntry()\ListIconID = ListIconID

  ; ----- Initialize callback for changing selection

  class_addMethod(DelegateClass,
    sel_registerName("tableViewSelectionDidChange:"),
    @SelectionDidChangeCallback(), "v@:@")
  CocoaMessage(0, NotificationCenter,
    "addObserver:", AppDelegate,
    "selector:", sel_registerName("tableViewSelectionDidChange:"),
    "name:$", @"NSTableViewSelectionDidChangeNotification",
    "object:", GadgetID(CallbackEntry()\ListIconID))

  ; ----- Initialize callback for header click

  class_addMethod(DelegateClass,
    sel_registerName("tableView:didClickTableColumn:"),
    @ColumnHeaderClickCallback(), "v@:@@")

  CocoaMessage(0, GadgetID(CallbackEntry()\ListIconID),
    "setDelegate:", AppDelegate)
EndProcedure

; ========================================== SPECIFIC CODE FOR TESTING

Enumeration
  #zero
  #windows_nb
  #list1_nb
  #list2_nb
EndEnumeration

#Title_column1$ = "Name"
#Title_column2$ = "Address"

Define GadgetID.I

OpenWindow(#windows_nb, 0, 0, 870, 110, "Detect left click on header cell",
  #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

ListIconGadget(#list1_nb, 10, 10, 420, WindowHeight(#windows_nb) - 20,
  #Title_column1$, 110, #PB_ListIcon_FullRowSelect)
AddGadgetColumn(#list1_nb, 1, #Title_column2$, 300)
AddGadgetItem(#list1_nb, -1, "Harry Rannit" + #LF$ +
  "12 Parliament Way, Battle Street, By the Bay")
AddGadgetItem(#list1_nb, -1, "Ginger Brokeit"+ #LF$ +
  "130 PureBasic Road, BigTown, CodeCity")
AddGadgetItem(#list1_nb, -1, "Didi Foundit"+ #LF$ +
  "321 Logo Drive, Mouse House, Downtown")

ListIconGadget(#list2_nb, 440, 10, 420, WindowHeight(#windows_nb) - 20,
  #Title_column1$, 110, #PB_ListIcon_FullRowSelect)
AddGadgetColumn(#list2_nb, 1, #Title_column2$, 300)
AddGadgetItem(#list2_nb, -1, "Harry Rannit" + #LF$ +
  "12 Parliament Way, Battle Street, By the Bay")
AddGadgetItem(#list2_nb, -1, "Ginger Brokeit"+ #LF$ +
  "130 PureBasic Road, BigTown, CodeCity")
AddGadgetItem(#list2_nb, -1, "Didi Foundit"+ #LF$ +
  "321 Logo Drive, Mouse House, Downtown")

SetGadgetCallback(#windows_nb, #list1_nb)
SetGadgetCallback(#windows_nb, #list2_nb)

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
    Case #PB_Event_Gadget
      GadgetID = EventGadget()

      Select GadgetID
        Case #list1_nb, #list2_nb
          Select EventType()
          Case #PB_EventType_LeftClick
            If EventData()
              Debug "Left click on header of column " + Str(EventData() - 1) +
                ", Gadget " + Str(GadgetID)
            Else
              Debug "-> Left click on row " + Str(GetGadgetState(GadgetID)) +
                ", Gadget " + Str(GadgetID)
            EndIf
          Case #PB_EventType_Change
            Debug "Selected row changed:"
          EndSelect
      EndSelect
  EndSelect
ForEver

CocoaMessage(0, NotificationCenter, "removeObserver:", AppDelegate)
Lebostein
Addict
Addict
Posts: 807
Joined: Fri Jun 11, 2004 7:07 am

Re: ListIconGadget: No #PB_EventType_Change!

Post by Lebostein »

Thanks!!!! That's it! :D
Lebostein
Addict
Addict
Posts: 807
Joined: Fri Jun 11, 2004 7:07 am

Re: ListIconGadget: No #PB_EventType_Change!

Post by Lebostein »

I hope Fred will add the header click event to PB one day. In my eyes an essential thing for list table gadgets...
Lebostein
Addict
Addict
Posts: 807
Joined: Fri Jun 11, 2004 7:07 am

Re: ListIconGadget: No #PB_EventType_Change!

Post by Lebostein »

But #PB_EventType_Change returns the wrong GadgetID !!!!

add the gadget ID to the output:

Code: Select all

Debug "Selected row changed, Gadget " + Str(GadgetID)
Start the code and click on the entries on left and right. The GadgetID is everytime = 3, whether you click left or right.
But if you click one time the header, then it suddenly works....
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: ListIconGadget: No #PB_EventType_Change!

Post by Shardik »

Lebostein wrote:But #PB_EventType_Change returns the wrong GadgetID !!!!

add the gadget ID to the output:

Code: Select all

Debug "Selected row changed, Gadget " + Str(GadgetID)
Start the code and click on the entries on left and right. The GadgetID is everytime = 3, whether you click left or right.
But if you click one time the header, then it suddenly works....
You are right, it's a bug. The callback SelectionDidChangeCallback() is a notification callback which doesn't receive in its parameters the currently selected ListIconGadget (TableView) and column object (Table Column) like the ColumnHeaderClickCallback(). So in SelectionDidChangeCallback() PostEvent() incorrectly sends the previously selected linked list elements WindowID and ListIconID.

To eleminate this bug please change in SelectionDidChangeCallback() this code

Code: Select all

    PostEvent(#PB_Event_Gadget, CallbackEntry()\WindowID,
      CallbackEntry()\ListIconID, #PB_EventType_Change)
against this modified one

Code: Select all

    PostEvent(#PB_Event_Gadget, GetActiveWindow(), GetActiveGadget(),
      #PB_EventType_Change)
I have tested the modified example successfully on MacOS 10.14.6 'Mojave' with PB 5.71 x64.
Lebostein
Addict
Addict
Posts: 807
Joined: Fri Jun 11, 2004 7:07 am

Re: ListIconGadget: No #PB_EventType_Change!

Post by Lebostein »

Thats it!! :D Thanks a million!
Lebostein
Addict
Addict
Posts: 807
Joined: Fri Jun 11, 2004 7:07 am

Re: ListIconGadget: No #PB_EventType_Change!

Post by Lebostein »

Edit: It seems the last change influences other gadgets with the #PB_EventType_Change event. What about that solution? It seems it works, but I don't know if it's right:

Code: Select all

ProcedureC SelectionDidChangeCallback(Object.I, Selector.I, Notification.I)
  Shared CallbackEntry()
  Static ChangeSignalled.I

  ForEach CallbackEntry()
    If GetActiveWindow() = CallbackEntry()\WindowID And GetActiveGadget() = CallbackEntry()\ListIconID
      If ChangeSignalled
        PostEvent(#PB_Event_Gadget, CallbackEntry()\WindowID, CallbackEntry()\ListIconID, #PB_EventType_Change)
        ChangeSignalled = #False
      Else
        ChangeSignalled = #True
      EndIf
    EndIf
  Next

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

Re: ListIconGadget: No #PB_EventType_Change!

Post by Shardik »

Lebostein wrote:Edit: It seems the last change influences other gadgets with the #PB_EventType_Change event. What about that solution? It seems it works, but I don't know if it's right:
Your solution should be even better than my proposition. Your solution takes care that the correct entry in the shared LinkedList CallbackEntry() is selected. Due to the sharing of the LinkedList CallbackEntry() in the callbacks, the change of an entry in a callback is also in effect in the main program.
Post Reply