How to detect listicongadget column header click
Posted: Thu Jan 30, 2014 4:38 pm
				
				I dont find this thread anymore where discussed about that?
Karu
			Karu
http://www.purebasic.com
https://www.purebasic.fr/english/
Please try the cross-platform code example below which detects the clicked header column and the clicked row. I have tested it successfully on these operating systems:CONVERT wrote:Freak's code works fine with PB 5.21, but it is Windows specific.
For Linux or Macintosh, I tried to see how to use BindEvent, but I did not find yet how to detect the column. It is not urgent, but I would like to use it in future versions of my application for Linux or Mac.
Thanks if you have any idea.
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
OpenWindow(0, 200, 100, 450, 150, "Detect left click on header cell")
ListIconGadget(0, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 20, "Name",
  110, #PB_ListIcon_FullRowSelect)
AddGadgetColumn(0, 1, "Address", 300)
AddGadgetItem(0, -1, "Harry Rannit" + #LF$ +
  "12 Parliament Way, Battle Street, By the Bay")
AddGadgetItem(0, -1, "Ginger Brokeit"+ #LF$ +
  "130 PureBasic Road, BigTown, CodeCity")
AddGadgetItem(0, -1, "Didi Foundit"+ #LF$ +
  "321 Logo Drive, Mouse House, Downtown")
SetGadgetCallback(0, 0)
Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
    Case #PB_Event_Gadget
      If EventGadget() = 0 And EventType() = #PB_EventType_LeftClick
        If EventData()
          Debug "Left click on header of column " + Str(EventData() - 1)
        Else
          Debug "Left click on row " + Str(GetGadgetState(0))
        EndIf
      EndIf
  EndSelect
ForEverCode: Select all
; VERSION 7
;How To detect listicongadget column header click
;by Shardik ยป Tue Mar 18, 2014 23:18
EnableExplicit
Define CallbackListIconID.I
Define CallbackWindowID.I
CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Linux ; ------------------------------------------------
    ProcedureC ColumnHeaderClickCallback(*Column, ClickedHeaderColumn.I)
      Shared CallbackListIconID.I
      Shared CallbackWindowID.I
      PostEvent(#PB_Event_Gadget, CallbackWindowID, CallbackListIconID,
        #PB_EventType_LeftClick, ClickedHeaderColumn + 1)
    EndProcedure
    Procedure SetGadgetCallback(WindowID.I, ListIconID.I)
      Shared CallbackWindowID.I
      Shared CallbackListIconID.I
      Protected Column.I
      Protected ColumnCount.I
      Protected ColumnIndex.I
      Protected *ListStore.GtkListStore
      CallbackWindowID = WindowID
      CallbackListIconID = ListIconID
      gtk_tree_view_set_headers_clickable_(GadgetID(CallbackListIconID), #True)
      *ListStore = gtk_tree_view_get_model_(GadgetID(CallbackListIconID))
      ColumnCount = (*ListStore\n_columns - 3) / 3
      For ColumnIndex = 0 To ColumnCount - 1
        Column = gtk_tree_view_get_column_(GadgetID(CallbackListIconID),
          ColumnIndex)
        If Column
          g_signal_connect_data_(Column, "clicked",
            @ColumnHeaderClickCallback(), 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 CallbackListIconID.I
      Shared CallbackWindowID.I
      Protected ClickedHeaderColumn.I
      
      ClickedHeaderColumn = Val(PeekS(CocoaMessage(0,
        CocoaMessage(0, TableColumn, "identifier"),
        "UTF8String"), -1, #PB_UTF8))
      PostEvent(#PB_Event_Gadget, CallbackWindowID, CallbackListIconID,
        #PB_EventType_LeftClick, ClickedHeaderColumn + 1)
    EndProcedure
    
    Procedure SetGadgetCallback(WindowID.I, ListIconID.I)
      Shared CallbackListIconID.I
      Shared CallbackWindowID.I
      
      Protected AppDelegate.I
      Protected DelegateClass.I
      Protected Selector.I = sel_registerName(ConvertToUTF8("tableView:didClickTableColumn:"))
      Protected Types.S = ConvertToUTF8("v@:@@")
      
      CallbackListIconID = ListIconID
      CallbackWindowID = WindowID
      AppDelegate = CocoaMessage(0,
        CocoaMessage(0, 0, "NSApplication sharedApplication"), "delegate")
      DelegateClass = CocoaMessage(0, AppDelegate, "class")
      class_addMethod(DelegateClass, Selector, @ColumnHeaderClickCallback(),
        Types)
      CocoaMessage(0, GadgetID(CallbackListIconID), "setDelegate:", AppDelegate)
    EndProcedure
  CompilerCase #PB_OS_Windows ; ----------------------------------------------
    Define DefaultListIconCallback.I
    Procedure ColumnHeaderClickCallback(WindowHandle.I, Msg.I, WParam.I,
      LParam.I)
      Shared DefaultListIconCallback.I
      Shared CallbackListIconID.I
      Shared CallbackWindowID.I
      Protected Result.I
      Protected *Header.HD_NOTIFY
      Result = CallWindowProc_(DefaultListIconCallback, WindowHandle, Msg,
        WParam, LParam)
      If Msg = #WM_NOTIFY
        *Header = LParam
        If *Header\hdr\code = #HDN_ITEMCLICK
          PostEvent(#PB_Event_Gadget, CallbackWindowID, CallbackListIconID,
            #PB_EventType_LeftClick, *Header\iItem + 1)
        EndIf
      EndIf
      ProcedureReturn Result
    EndProcedure
    Procedure SetGadgetCallback(WindowID.I, ListIconID.I)
      Shared DefaultListIconCallback.I
      Shared CallbackListIconID.I
      Shared CallbackWindowID.I
      CallbackListIconID = ListIconID
      CallbackWindowID = WindowID
      DefaultListIconCallback = SetWindowLong_(GadgetID(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"
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)
Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #list1_nb, #list2_nb
          If EventType() = #PB_EventType_LeftClick
            If EventData()
              Debug "Left click on header of column " + Str(EventData() - 1) + " gadget=" + Str(EventGadget())
            Else
              Debug "Left click on row " + Str(GetGadgetState(EventGadget())) + " gadget=" + Str(EventGadget())
            EndIf
          EndIf
      EndSelect
  EndSelect
ForEver
Code: Select all
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #list1_nb, #list2_nb
          If EventType() = #PB_EventType_LeftClick
            If EventData()
              Debug "Left click on header of column " + Str(EventData() - 1) + " gadget=" + Str(EventGadget())Code: Select all
    Case #PB_Event_Gadget
      GadgetID = EventGadget()
      Select GadgetID
        Case #list1_nb, #list2_nb
          If EventType() = #PB_EventType_LeftClick
            If EventData()
              Debug "Left click on header of column " + Str(EventData() - 1) + " gadget=" + Str(GadgetID)
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)
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
          If EventType() = #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
          EndIf
      EndSelect
  EndSelect
ForEverCode: Select all
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
ForEverI have posted a modified version for MacOS here which also generates #PB_EventType_Change events.Lebostein wrote:There seems a bug in the Mac OS version. This code deactivates the #PB_EventType_Change event!!