EnableGadgetDrag for ListIconGadget and ListViewGadget

Mac OSX specific forum
User avatar
mk-soft
Always Here
Always Here
Posts: 6244
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

EnableGadgetDrag for ListIconGadget and ListViewGadget

Post by mk-soft »

The dragging of ListIconGadget and ListViewGadget has unfortunately not worked for a long time.
Here is a solution.

Update v1.02.1
- Bugfixes

Update v1.02.2
- Bugfix Mouse position

Update v1.03.1
- Change NSString to NSPasteboardItem

Code: Select all

;-TOP Dump Object Methods

; by mk-soft, 29.12.2019 - 06.10.2023, v1.09.1

Declare.s DumpObjectMethods(*Object, SuperLevel = 0, HidePrivate = #True, ShowEncoding = #False, FirstArgument = 2)

Structure ArrayOfMethods
  i.i[0]
EndStructure

ImportC ""
  class_copyMethodList(*Class, *p_methodCount)
  ; -> An array of pointers of type Method describing
  ;    the instance methods implemented by the class
  ;    Any instance methods implemented by superclasses are Not included
  ;    You must free the array with free()
  class_getName(*Class) ; -> UnsafePointer<Int8> -> *string
  sel_getName(*Selector); -> const char *
  method_getName(*Method) ; -> Selector
  method_getTypeEncoding(*Method) ; -> const char *
  method_getReturnType(*Method, *dst, dst_len) ; -> void
  method_getNumberOfArguments(*Method)         ; -> unsigned int
  method_getArgumentType(*Method, index, *dst, dst_len) ; -> void
  
  NSGetSizeAndAlignment(*StringPtr, *p_size, *p_align) 
  ; -> const char *
  ;    Obtains the actual size and the aligned size of an encoded type.
EndImport

; ----

Procedure.s GetArgumentType(*String)
  Protected r1.s, arg.s, size.i, ofs.i
  
  arg = PeekS(*String, -1, #PB_UTF8)
  r1 + arg + " - "
  If Left(arg, 1) = "^"
    r1 + "A pointer to type of "
    arg = Mid(arg, 2)
  EndIf
  Select arg
    Case "c" : r1 + "A char "
    Case "i" : r1 + "An int "
    Case "s" : r1 + "A short "
    Case "l" : r1 + "A long "
    Case "q" : r1 + "A long long"
    Case "C" : r1 + "An unsigned char "
    Case "I" : r1 + "An unsigned int "
    Case "S" : r1 + "An unsigned short "
    Case "L" : r1 + "An unsigned long "
    Case "Q" : r1 + "An unsigned long long "
    Case "f" : r1 + "A float "
    Case "d" : r1 + "A double "
    Case "B" : r1 + "A C++ bool Or a C99 _Bool "
    Case "v" : r1 + "A void"
    Case "*" : r1 + "A character string (char *) "
    Case "@" : r1 + "An object (whether statically typed Or typed id) "
    Case "#" : r1 + "A class object (Class) "
    Case ":" : r1 + "A method selector (SEL) "
    Default:
      NSGetSizeAndAlignment(*String, @size, @ofs)
      r1 + "[" + Str(size) + " bytes]"
  EndSelect
  If Right(arg, 1) = "?"
    r1 + "An unknown type (e.g. function pointer)"
  EndIf
  ProcedureReturn r1
EndProcedure

; ----

Procedure.s DumpObjectMethods(*Object, SuperLevel = 0, HidePrivate = #True, ShowEncoding = #False, FirstArgument = 2)
  Protected result.s, r1.s, i, c, n, methodCount, Method.s
  Protected *Class, *SuperClass, *Method, *Methods.ArrayOfMethods
  Protected *String
  
  *Class = object_getclass_(*Object)
  If *Class
    *String = AllocateMemory(1024)
    r1 = PeekS(class_getName(*Class), -1, #PB_UTF8)
    If SuperLevel
      For i = 1 To SuperLevel
        *SuperClass = class_getsuperclass_(*Class)
        If *SuperClass
          *Class = *SuperClass
          r1 + " -> " + PeekS(class_getName(*Class), -1, #PB_UTF8)
        Else
          Break
        EndIf
      Next
    EndIf
    *Methods = class_copyMethodList(*Class, @methodCount)
    r1 + #LF$ + #LF$ + "Count of Methods: " + methodCount + #LF$
    result = r1 + #LF$
    Debug r1
    r1 = ""
    For i = 0 To methodCount - 1
      *Method = *Methods\i[i];
      Method = PeekS(sel_getName(method_getName(*Method)), -1, #PB_UTF8)
      If HidePrivate And Left(Method, 1) = "_"
        Continue
      EndIf
      r1 + "Method " + Method + #LF$
      If ShowEncoding
        r1 + " * Encoding " + PeekS(method_getTypeEncoding(*Method), -1, #PB_UTF8) + #LF$
      EndIf
      method_getReturnType(*Method, *String, 1024)
      r1 + " -- ReturnType = " + GetArgumentType(*String) + #LF$
      c = method_getNumberOfArguments(*Method)
      For n = FirstArgument To c - 1
        method_getArgumentType(*Method, n, *String, 1024)
        r1 + " -- Argument " + Str(n - FirstArgument + 1) + " = " + GetArgumentType(*String) + #LF$
      Next
      result + r1 + #LF$
      Debug r1
      r1 = ""
    Next
    r1 + "End Class" + #LF$
    result + r1 + #LF$
    Debug r1
    If *Methods
      free_(*Methods)
    EndIf
    FreeMemory(*String)
  Else
    r1 = "Object is nil" + #LF$
    result = r1
    Debug r1
  EndIf
  ProcedureReturn result
EndProcedure

; ****

;-TOP EnableGadgetDrag

; Comment : EnableGadgetDrag for ListIconGadget and ListViewGadget
; Author  : mk-soft
; Version : v1.03.1
; Create  : 19.01.2025
; Update  : 22.01.2025

; ----

ProcedureC MyTableViewDraggingSessionWillBeginAtPointForRowIndexes(Object, Selector, Table, Session, IndexSet, PointX.d, PointY.d)
  ;TODO Create drag image
EndProcedure

; ----

; Own delegate method tableView:pasteboardWriterForRow:

Global *NSPasteboardTypeString.Integer = dlsym_(#RTLD_DEFAULT, "NSPasteboardTypeString")

ProcedureC MyTableViewPasteboardWriterForRow(Object, Selector, Table, Row)
  Protected window, gadget, column, text.s, count, pt.nspoint, string
  
  ; Check that the column is valid, otherwise no dragging image can be created and an error will occur.
  window = GetActiveWindow()
  gadget = CocoaMessage(0, Table, "tag")
  
  If window >= 0
    pt\x = WindowMouseX(window) - GadgetX(gadget) 
    pt\y = WindowMouseY(window) - GadgetY(gadget)
    column = CocoaMessage(0, Table, "columnAtPoint:@", @pt)
    If column >= 0
      text = GetGadgetItemText(gadget, row, 0)
      ; Copy all columns
      If GadgetType(gadget) = #PB_GadgetType_ListIcon
        count = GetGadgetAttribute(gadget, #PB_ListIcon_ColumnCount) - 1
        For column = 1 To count
          text + #TAB$ + GetGadgetItemText(gadget, row, column)
        Next
      EndIf
      ; Return value is a pasteboard item
      pasteboarditem = CocoaMessage(0, 0, "NSPasteboardItem new")
      CocoaMessage(0, pasteboarditem, "setString:$", @text, "forType:", *NSPasteboardTypeString\i)
      ProcedureReturn pasteboarditem
   EndIf
   ; Cancel dragging
   ProcedureReturn 0
  EndIf
EndProcedure

; ----

Procedure EnableGadgetDrag(Gadget)
  Static oldClassListIcon, newClassListIcon
  Static oldClassListView, newClassListView
  
  Protected NSTableView = GadgetID(Gadget)
  Protected delegate, class, newClass, oldClass, selector
  
  ; Get delegate class
  delegate = CocoaMessage(0, NSTableView, "delegate")
  class = object_getClass_(delegate)
      
  Select GadgetType(Gadget)
    Case #PB_GadgetType_ListIcon
      ; Check is method exists
      If Not newClassListIcon
        ; Create new delegate class
        newClassListIcon = objc_allocateClassPair_(class, "PBListIconGadgetFunctionsEx", 0)
        objc_registerClassPair_(newClassListIcon)
        ; Add new method
        selector = sel_registerName_("tableView:pasteboardWriterForRow:")
        imp = class_addMethod_(newClassListIcon, selector, @MyTableViewPasteboardWriterForRow(), "@@:@i")
        selector = sel_registerName_("tableView:draggingSession:willBeginAtPoint:forRowIndexes:")
        imp = class_addMethod_(newClassListIcon, selector, @MyTableViewDraggingSessionWillBeginAtPointForRowIndexes(), "@@:@@@@")
        oldClassListIcon = object_setClass_(delegate, newClassListIcon)
      Else
        If class = oldClassListIcon
          object_setClass_(delegate, newClassListIcon)
        EndIf
      EndIf
      ProcedureReturn newClassListIcon
      
    Case #PB_GadgetType_ListView
      ; Check is method exists
      If Not newClassListView
        ; Create new delegate class
        newClassListView = objc_allocateClassPair_(class, "PBListViewGadgetFunctionsEx", 0)
        objc_registerClassPair_(newClassListView)
        ; Add new method
        selector = sel_registerName_("tableView:pasteboardWriterForRow:")
        imp = class_addMethod_(newClassListView, selector, @MyTableViewPasteboardWriterForRow(), "@@:@i")
        selector = sel_registerName_("tableView:draggingSession:willBeginAtPoint:forRowIndexes:")
        imp = class_addMethod_(newClassListView, selector, @MyTableViewDraggingSessionWillBeginAtPointForRowIndexes(), "@@:@@@@")
        oldClassListView = object_setClass_(delegate, newClassListView)
      Else
        If class = oldClassListView
          object_setClass_(delegate, newClassListView)
        EndIf
      EndIf
      ProcedureReturn newClassListView
      
    Default
      ProcedureReturn 0
  EndSelect
EndProcedure

; ********

;- Example

CompilerIf #PB_Compiler_IsMainFile
  
  Procedure DoEventDropText()
    Protected droptext.s, text.s, cnt, i
    droptext = EventDropText()
    cnt = CountString(droptext, #LF$) + 1
    For i = 1 To cnt
      text = StringField(droptext, i, #LF$)
      ReplaceString(text, #TAB$, #LF$, #PB_String_InPlace)
      AddGadgetItem(EventGadget(), -1, text)
    Next
  EndProcedure
  
  ; ----
  
  Procedure UpdateWindow()
    Protected dx, dy
    dx = WindowWidth(0)
    dy = WindowHeight(0) - StatusBarHeight(0) - MenuHeight()
    ; Resize Gadgets
    ResizeGadget(0, 0, 0, dx / 2, dy)
    ResizeGadget(1, dx / 2, 0, dx / 2, dy)
    
  EndProcedure
  
  Procedure Main()
    Protected dx, dy
    
    #WinStyle = #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget
    
    If OpenWindow(0, #PB_Ignore, #PB_Ignore, 1200, 400, "Test Window Drag and Drop Text", #WinStyle)
      ; MenuBar
      CreateMenu(0, WindowID(0))
      MenuTitle("&File")
      MenuItem(99, "E&xit")
      
      ; StatusBar
      CreateStatusBar(0, WindowID(0))
      AddStatusBarField(#PB_Ignore)
      
      ; Gadgets
      dx = WindowWidth(0)
      dy = WindowHeight(0) - StatusBarHeight(0) - MenuHeight()
      
      ListIconGadget(0, 0, 0, dx / 2, dy, "Column 0", 200, #PB_ListIcon_MultiSelect)
      AddGadgetColumn(0, 1, "Column 1", 200)
      ListIconGadget(1, dx / 2, 0, dx / 2, dy, "Column 0", 200)
      AddGadgetColumn(1, 1, "Column 1", 200)
      
      ;ListViewGadget(1, dx / 2, 0, dx / 2, dy)
      
      For i = 0 To 9
        AddGadgetItem(0, -1, "Item 0." + i + #LF$ + "Item 1." + i)
      Next
      
      ; Bind Events
      BindEvent(#PB_Event_SizeWindow, @UpdateWindow(), 0)
      BindEvent(#PB_Event_GadgetDrop, @DoEventDropText())
      
      EnableGadgetDrag(0)
      EnableGadgetDrag(1)
      
      EnableGadgetDrop(0, #PB_Drop_Text, #PB_Drag_Copy)
      EnableGadgetDrop(1, #PB_Drop_Text, #PB_Drag_Copy)
      
      ;delegate = CocoaMessage(0, GadgetID(0), "delegate")
      ;DumpObjectMethods(delegate, 0)
      ;DumpObjectMethods(delegate, 1)
      
      ; Main Loop
      Repeat
        Select WaitWindowEvent()
          Case #PB_Event_CloseWindow
            Select EventWindow()
              Case 0
                Break
            EndSelect
            
          Case #PB_Event_Menu
            Select EventMenu()
              Case 99
                PostEvent(#PB_Event_CloseWindow, 0, 0)
                
            EndSelect
            
          Case #PB_Event_Gadget
            Select EventGadget()
                
            EndSelect
            
        EndSelect
      ForEver
      
    EndIf
    
  EndProcedure : Main()
  
CompilerEndIf
Last edited by mk-soft on Sat Feb 01, 2025 6:19 pm, edited 3 times in total.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
mk-soft
Always Here
Always Here
Posts: 6244
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: EnableGadgetDrag for ListIconGadget and ListViewGadget

Post by mk-soft »

Update v1.02.1
- Bugfixes

Update v1.02.2
- Bugfix Mouse position

The new delegate class only needs to be created once ;)
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
Post Reply