/* Cockos SWELL (Simple/Small Win32 Emulation Layer for Linux/OSX) Copyright (C) 2006 and later, Cockos, Inc. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This file provides basic windows APIs for handling windows, as well as the stubs to enable swell-dlggen to work. */ #ifndef SWELL_PROVIDED_BY_APP #import #import #include "swell.h" #include "../mutex.h" #include "../ptrlist.h" #include "../queue.h" #include "../wdlcstring.h" #include "swell-dlggen.h" #define SWELL_INTERNAL_MERGESORT_IMPL #define SWELL_INTERNAL_HTREEITEM_IMPL #include "swell-internal.h" static bool SWELL_NeedModernListViewHacks() { #ifdef __LP64__ return false; #else // only needed on 32 bit yosemite as of 10.10.3, but who knows when it will be necessary elsewhere return SWELL_GetOSXVersion() >= 0x10a0; #endif } static LRESULT sendSwellMessage(id obj, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (obj && [obj respondsToSelector:@selector(onSwellMessage:p1:p2:)]) return [(SWELL_hwndChild *)obj onSwellMessage:uMsg p1:wParam p2:lParam]; return 0; } static void InvalidateSuperViews(NSView *view); #define STANDARD_CONTROL_NEEDSDISPLAY_IMPL(classname) \ - (const char *)getSwellClass { return ( classname ); } \ - (void)setNeedsDisplay:(BOOL)flag \ { \ [super setNeedsDisplay:flag]; \ if (flag) InvalidateSuperViews(self); \ } \ - (void)setNeedsDisplayInRect:(NSRect)rect \ { \ [super setNeedsDisplayInRect:rect]; \ InvalidateSuperViews(self); \ } static WDL_PtrList s_prefix_removals; int g_swell_osx_readonlytext_wndbg = 0; int g_swell_osx_style = 0; // &1 = rounded buttons, &2=big sur styled lists static void *SWELL_CStringToCFString_FilterPrefix(const char *str) { int c=0; while (str[c] && str[c] != '&' && c++<1024); if (!str[c] || c>=1024 || strlen(str)>=1024) return SWELL_CStringToCFString(str); char buf[1500]; { const char *p=str; char *op=buf; while (*p) { if (*p == '&') p++; if (!*p) break; *op++=*p++; } *op=0; } // add to recent prefix removal cache for localization if (WDL_NOT_NORMALLY(s_prefix_removals.GetSize() > 256)) s_prefix_removals.Delete(0,true,free); { const size_t sz1 = strlen(buf), sz2 = strlen(str); char *p = (char *)malloc(sz1+sz2+2); if (WDL_NORMALLY(p!=NULL)) { memcpy(p,buf,sz1+1); memcpy(p+sz1+1,str,sz2+1); s_prefix_removals.Add(p); } } return SWELL_CStringToCFString(buf); } static int _nsStringSearchProc(const void *_a, const void *_b) { NSString *a=(NSString *)_a; NSString *b = (NSString *)_b; return (int)[a compare:b]; } static int _nsMenuSearchProc(const void *_a, const void *_b) { NSString *a=(NSString *)_a; NSMenuItem *b = (NSMenuItem *)_b; return (int)[a compare:[b title]]; } static int _listviewrowSearchFunc(const void *_a, const void *_b, const void *ctx) { const char *a = (const char *)_a; SWELL_ListView_Row *row = (SWELL_ListView_Row *)_b; const char *b=""; if (!row || !(b=row->get_col_txt(0))) b=""; return strcmp(a,b); } static int _listviewrowSearchFunc2(const void *_a, const void *_b, const void *ctx) { const char *a = (const char *)_a; SWELL_ListView_Row *row = (SWELL_ListView_Row *)_b; const char *b=""; if (!row || !(b=row->get_col_txt(0))) b=""; return strcmp(b,a); } // modified bsearch: returns place item SHOULD be in if it's not found static NSInteger arr_bsearch_mod(void *key, NSArray *arr, int (*compar)(const void *, const void *)) { const NSInteger nmemb = [arr count]; NSInteger p,lim,base=0; for (lim = nmemb; lim != 0; lim >>= 1) { p = base + (lim >> 1); int cmp = compar(key, [arr objectAtIndex:p]); if (cmp == 0) return (p); if (cmp > 0) { /* key > p: move right */ // check to see if key is less than p+1, if it is, we're done base = p + 1; if (base >= nmemb || compar(key,[arr objectAtIndex:base])<=0) return base; lim--; } /* else move left */ } return 0; } template static int ptrlist_bsearch_mod(void *key, WDL_PtrList *arr, int (*compar)(const void *, const void *, const void *ctx), void *ctx) { const int nmemb = arr->GetSize(); int base=0, lim, p; for (lim = nmemb; lim != 0; lim >>= 1) { p = base + (lim >> 1); int cmp = compar(key, arr->Get(p),ctx); if (cmp == 0) return (p); if (cmp > 0) { /* key > p: move right */ // check to see if key is less than p+1, if it is, we're done base = p + 1; if (base >= nmemb || compar(key,arr->Get(base),ctx)<=0) return base; lim--; } /* else move left */ } return 0; } @implementation SWELL_TabView STANDARD_CONTROL_NEEDSDISPLAY_IMPL("SysTabControl32") -(void)setNotificationWindow:(id)dest { m_dest=dest; } -(id)getNotificationWindow { return m_dest; } -(NSInteger) tag { return m_tag; } -(void) setTag:(NSInteger)tag { m_tag=tag; } - (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem { if (m_dest) { NMHDR nm={(HWND)self,(UINT_PTR)[self tag],TCN_SELCHANGE}; SendMessage((HWND)m_dest,WM_NOTIFY,nm.idFrom,(LPARAM)&nm); } } @end @implementation SWELL_ProgressView STANDARD_CONTROL_NEEDSDISPLAY_IMPL("msctls_progress32") -(NSInteger) tag { return m_tag; } -(void) setTag:(NSInteger)tag { m_tag=tag; } -(LRESULT)onSwellMessage:(UINT)msg p1:(WPARAM)wParam p2:(LPARAM)lParam { if (msg == PBM_SETRANGE) { [self setMinValue:LOWORD(lParam)]; [self setMaxValue:HIWORD(lParam)]; } else if (msg==PBM_SETPOS) { [self setDoubleValue:(double)wParam]; [self stopAnimation:self]; } else if (msg==PBM_DELTAPOS) { [self incrementBy:(double)wParam]; } return 0; } @end @implementation SWELL_ListViewCell -(NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { if ([controlView isKindOfClass:[SWELL_ListView class]]) { if (((SWELL_ListView *)controlView)->m_selColors) return nil; } else if ([controlView isKindOfClass:[SWELL_TreeView class]]) { if (((SWELL_TreeView *)controlView)->m_selColors) return nil; } return [super highlightColorWithFrame:cellFrame inView:controlView]; } - (NSRect)drawingRectForBounds:(NSRect)rect { const NSSize sz = [self cellSizeForBounds:rect]; rect = [super drawingRectForBounds:rect]; const int offs = (int) floor((rect.size.height - sz.height) * .5); if (offs>0) { rect.origin.y += offs; rect.size.height -= offs*2; } return rect; } @end @implementation SWELL_StatusCell -(id)initNewCell:(bool)always_indent { if ((self=[super initTextCell:@""])) { m_always_indent=always_indent; status=0; } return self; } -(void)setStatusImage:(NSImage *)img { status=img; } - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { if (status) { const double fr_h = cellFrame.size.height; const NSSize image_sz = [status size]; const double use_h = wdl_min(image_sz.height, fr_h); const double yo = (fr_h-use_h)*.5; double use_w,xo; if (use_h > cellFrame.size.width) { use_w = cellFrame.size.width; xo = 0.0; } else { use_w = use_h; xo = yo; } [status drawInRect:NSMakeRect(cellFrame.origin.x + xo,cellFrame.origin.y + yo,use_w,use_h) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; } if (m_always_indent || status) { cellFrame.origin.x += cellFrame.size.height + 2.0; cellFrame.size.width -= cellFrame.size.height + 2.0; } [super drawWithFrame:cellFrame inView:controlView]; } @end static HTREEITEM FindTreeItemByDataHold(const WDL_PtrList *list, SWELL_DataHold *srch) { for (int x = 0; x < list->GetSize(); x ++) { HTREEITEM item = list->Get(x); if (item && item->m_dh == srch) return item; } for (int x = 0; x < list->GetSize(); x ++) { HTREEITEM item = list->Get(x); if (item && item->m_children.GetSize()) { item = FindTreeItemByDataHold(&item->m_children,srch); if (item) return item; } } return NULL; } @implementation SWELL_TreeView STANDARD_CONTROL_NEEDSDISPLAY_IMPL("SysTreeView32") -(id) init { if ((self = [super init])) { m_fakerightmouse=false; m_items=new WDL_PtrList; m_fgColor=0; m_selColors=0; } return self; } -(void) dealloc { if (m_items) m_items->Empty(true); delete m_items; m_items=0; [m_fgColor release]; [m_selColors release]; [super dealloc]; } -(bool) findItem:(HTREEITEM)item parOut:(HTREEITEM__ **)par idxOut:(int *)idx { if (!m_items||!item) return false; int x=m_items->Find((HTREEITEM__*)item); if (x>=0) { *par=NULL; *idx=x; return true; } for (x = 0; x < m_items->GetSize(); x++) { if (m_items->Get(x)->FindItem(item,par,idx)) return true; } return false; } -(NSInteger) outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { if (item == nil) return m_items ? m_items->GetSize() : 0; return ((HTREEITEM__*)[item getValue])->m_children.GetSize(); } - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { if (item==nil) return YES; HTREEITEM__ *it=(HTREEITEM__ *)[item getValue]; return it && it->m_haschildren; } - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { HTREEITEM__ *row=item ? ((HTREEITEM__*)[item getValue])->m_children.Get(index) : m_items ? m_items->Get(index) : 0; return (id)(row ? row->m_dh : NULL); } - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { if (!item) return @""; HTREEITEM__ *it=(HTREEITEM__ *)[item getValue]; if (!it || !it->m_value) return @""; NSString *str=(NSString *)SWELL_CStringToCFString(it->m_value); return [str autorelease]; } - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pasteboard { if (self->style & TVS_DISABLEDRAGDROP) return NO; [pasteboard declareTypes:[NSArray arrayWithObject:@"swell_treeview"] owner:nil]; [pasteboard setString:@"" forType:@"swell_treeview"]; return YES; } - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id)info item:(id)item childIndex:(NSInteger)index { HWND par = GetParent((HWND)self); if (par && GetCapture() == par) { POINT p; GetCursorPos(&p); ScreenToClient(par,&p); SendMessage(par,WM_LBUTTONUP,0,MAKELPARAM(p.x,p.y)); } return YES; } /* - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item { return NO; // optionally while dragging? } */ - (void)outlineView:(NSOutlineView *)outlineView draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation { [self unregisterDraggedTypes]; HWND par = GetParent((HWND)self); if (par && GetCapture() == par) { // usually acceptDrop above will be the one that is called, but if the user ended up elsewhere // this might, let the caller clean up capture POINT p; GetCursorPos(&p); ScreenToClient(par,&p); SendMessage(par,WM_LBUTTONUP,0,MAKELPARAM(p.x,p.y)); } } - (void)outlineView:(NSOutlineView *)outlineView draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint forItems:(NSArray *)draggedItems { if (self->style & TVS_DISABLEDRAGDROP) return; HWND hwnd = (HWND)self, par = GetParent(hwnd); if (par) { HTREEITEM hit = NULL; if (m_items && [draggedItems count] > 0) { id obj = [draggedItems objectAtIndex:0]; if ([obj isKindOfClass:[SWELL_DataHold class]]) hit = FindTreeItemByDataHold(m_items, (SWELL_DataHold *)obj); } if (!hit) { TVHITTESTINFO tht; memset(&tht,0,sizeof(tht)); GetCursorPos(&tht.pt); ScreenToClient(hwnd, &tht.pt); hit = TreeView_HitTest(hwnd, &tht); } HTREEITEM sel = TreeView_GetSelection(hwnd); if (hit && hit != sel) { TreeView_SelectItem(hwnd,hit); sel = hit; } NMTREEVIEW nm={{hwnd,(UINT_PTR)[self tag],TVN_BEGINDRAG},}; nm.itemNew.hItem = sel; nm.itemNew.lParam = sel ? sel->m_param : 0; SendMessage(par,WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm); if (GetCapture() == par) [self registerForDraggedTypes:[NSArray arrayWithObject: @"swell_treeview"]]; } } - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id)info proposedItem:(id)item proposedChildIndex:(NSInteger)index { HWND hwnd=(HWND)self, par = GetParent(hwnd); if (par && GetCapture()==par) { POINT p; GetCursorPos(&p); TVHITTESTINFO tht; memset(&tht,0,sizeof(tht)); tht.pt = p; ScreenToClient(par,&p); LRESULT move_res = SendMessage(par,WM_MOUSEMOVE,0,MAKELPARAM(p.x,p.y)); if (move_res == (LRESULT)-1) return NSDragOperationNone; if (move_res == (LRESULT)-2) // move to end { HTREEITEM par_item = NULL; HTREEITEM li = self->m_items ? self->m_items->Get(self->m_items->GetSize()-1) : NULL; while (li && li->m_children.GetSize()) { par_item = li; li = li->m_children.Get(li->m_children.GetSize()-1); } if (par_item && par_item->m_children.GetSize()) [self setDropItem:par_item->m_dh dropChildIndex:par_item->m_children.GetSize()]; } else if (move_res >= 65536) { HTREEITEM paritem = NULL; int idx=0; // it is safe (but time consuming!) to call findItem: on a possibly-junk pointer if ([self findItem:(HTREEITEM)(INT_PTR)move_res parOut:&paritem idxOut:&idx] && paritem) [self setDropItem:paritem->m_dh dropChildIndex:idx]; } return NSDragOperationPrivate; } return NSDragOperationNone; } -(void)mouseDown:(NSEvent *)theEvent { if (([theEvent modifierFlags] & NSControlKeyMask) && IsRightClickEmulateEnabled()) { m_fakerightmouse=1; } else { NMCLICK nmlv={{(HWND)self,(UINT_PTR)[self tag], NM_CLICK},}; SendMessage((HWND)[self target],WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv); m_fakerightmouse=0; [super mouseDown:theEvent]; } } -(void)mouseDragged:(NSEvent *)theEvent { } -(void)mouseUp:(NSEvent *)theEvent { if (m_fakerightmouse||([theEvent modifierFlags] & NSControlKeyMask)) [self rightMouseUp:theEvent]; else [super mouseUp:theEvent]; } - (void)rightMouseUp:(NSEvent *)theEvent { bool wantContext=true; NMCLICK nmlv={{(HWND)self,(UINT_PTR)[self tag], NM_RCLICK},}; if (SendMessage((HWND)[self target],WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv)) wantContext=false; if (wantContext) { POINT p; GetCursorPos(&p); SendMessage((HWND)[self target],WM_CONTEXTMENU,(WPARAM)self,MAKELONG(p.x&0xffff,p.y)); } m_fakerightmouse=0; } - (void)highlightSelectionInClipRect:(NSRect)theClipRect { if (m_selColors) { int a = GetFocus() == (HWND)self ? 0 : 2; if ([m_selColors count] >= a) { NSColor *c=[m_selColors objectAtIndex:a]; if (c) { // calculate rect of selected items, combine with theClipRect, and fill these areas with our background (phew!) NSInteger x = [self selectedRow]; if (x>=0) { NSRect r = [self rectOfRow:x]; r = NSIntersectionRect(r,theClipRect); if (r.size.height>0 && r.size.width>0) { [c setFill]; NSRectFill(r); } } return ; } } } return [super highlightSelectionInClipRect:theClipRect]; } @end @implementation SWELL_ListView STANDARD_CONTROL_NEEDSDISPLAY_IMPL( m_lbMode ? "SysListView32_LB" : "SysListView32" ) -(BOOL)accessibilityPerformShowMenu { HWND par = (HWND)[self target]; if (par) { int row = (int)ListView_GetNextItem((HWND)self,-1,LVNI_FOCUSED); int col = 0; // todo NMLISTVIEW nmlv={{(HWND)self,(UINT_PTR)[self tag], NM_RCLICK}, row, col, 0, 0, 0, }; SendMessage(par,WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv); return YES; } return NO; } -(LONG)getSwellStyle { return style; } -(void)setSwellStyle:(LONG)st { bool hdrchg= ((style&LVS_NOCOLUMNHEADER) != (st&LVS_NOCOLUMNHEADER)); style=st; if ((style&LVS_REPORT) && hdrchg) { // todo some crap with NSTableView::setHeaderView, but it's complicated } } -(id) init { if ((self = [super init])) { m_subitem_images = false; m_selColors=0; m_fgColor = 0; ownermode_cnt=0; m_status_imagelist_type=-1; m_status_imagelist=0; m_leftmousemovecnt=0; m_fakerightmouse=false; m_lbMode=0; m_fastClickMask=0; m_last_shift_clicked_item = m_last_plainly_clicked_item=-1; m_start_item=-1; m_start_subitem=-1; m_start_item_clickmode=0; // 0=clicked item, 1=clicked image, &2=sent drag message, &4=quickclick mode m_cols = new WDL_PtrList; m_items=new WDL_PtrList; } return self; } -(void) dealloc { if (m_items) m_items->Empty(true); delete m_items; delete m_cols; m_cols=0; m_items=0; [m_fgColor release]; [m_selColors release]; [super dealloc]; } -(int)getColumnPos:(int)idx // get current position of column that was originally at idx { int pos=idx; if (m_cols) { NSTableColumn* col=m_cols->Get(idx); if (col) { NSArray* arr=[self tableColumns]; if (arr) { pos=(int)[arr indexOfObject:col]; } } } return pos; } - (void)highlightSelectionInClipRect:(NSRect)theClipRect { if (m_selColors) { int a = GetFocus() == (HWND)self ? 0 : 2; if ([m_selColors count] >= a) { NSColor *c=[m_selColors objectAtIndex:a]; if (c) { // calculate rect of selected items, combine with theClipRect, and fill these areas with our background (phew!) bool needfillset=true; NSInteger x = [self rowAtPoint:NSMakePoint(0,theClipRect.origin.y)]; if (x<0)x=0; const NSInteger n = [self numberOfRows]; for (;x = theClipRect.origin.y + theClipRect.size.height) break; if ([self isRowSelected:x]) { r = NSIntersectionRect(r,theClipRect); if (r.size.height>0 && r.size.width>0) { if (needfillset) { needfillset=false; [c setFill]; } NSRectFill(r); } } } return ; } } } return [super highlightSelectionInClipRect:theClipRect]; } -(int)getColumnIdx:(int)pos // get original index of column that is currently at position { int idx=pos; NSArray* arr=[self tableColumns]; if (arr && pos>=0 && pos < [arr count]) { NSTableColumn* col=[arr objectAtIndex:pos]; if (col && m_cols) { idx=m_cols->Find(col); } } return idx; } -(NSInteger)columnAtPoint:(NSPoint)pt { int pos=(int)[super columnAtPoint:pt]; return (NSInteger) [self getColumnIdx:pos]; } - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { return (!m_lbMode && (style & LVS_OWNERDATA)) ? ownermode_cnt : (m_items ? m_items->GetSize():0); } - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { NSString *str=NULL; int image_idx=0; if (!m_lbMode && (style & LVS_OWNERDATA)) { HWND tgt=(HWND)[self target]; char buf[1024]; NMLVDISPINFO nm={{(HWND)self, (UINT_PTR)[self tag], LVN_GETDISPINFO}}; nm.item.mask=LVIF_TEXT; if (m_status_imagelist_type==LVSIL_STATE) nm.item.mask |= LVIF_STATE; else if (m_status_imagelist_type == LVSIL_SMALL) nm.item.mask |= LVIF_IMAGE; nm.item.iImage = -1; nm.item.iItem=(int)rowIndex; nm.item.iSubItem=m_cols->Find(aTableColumn); nm.item.pszText=buf; nm.item.cchTextMax=sizeof(buf)-1; buf[0]=0; SendMessage(tgt,WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm); if (m_status_imagelist_type == LVSIL_STATE) image_idx=(nm.item.state>>16)&0xff; else if (m_status_imagelist_type == LVSIL_SMALL) image_idx = nm.item.iImage + 1; str=(NSString *)SWELL_CStringToCFString(nm.item.pszText); } else { const char *p=NULL; SWELL_ListView_Row *r=0; if (m_items && m_cols && (r=m_items->Get(rowIndex))) { int col_idx = m_cols->Find(aTableColumn); p=r->get_col_txt(col_idx); if (m_status_imagelist_type == LVSIL_STATE || m_status_imagelist_type == LVSIL_SMALL) { if (col_idx==0 || m_subitem_images) image_idx=r->get_img_idx(col_idx); } } str=(NSString *)SWELL_CStringToCFString(p); if (style & LBS_OWNERDRAWFIXED) { SWELL_ODListViewCell *cell=[aTableColumn dataCell]; if ([cell isKindOfClass:[SWELL_ODListViewCell class]]) [cell setItemIdx:(int)rowIndex]; } } if (!m_lbMode && m_status_imagelist) { SWELL_StatusCell *cell=(SWELL_StatusCell*)[aTableColumn dataCell]; if ([cell isKindOfClass:[SWELL_StatusCell class]]) { HICON icon=m_status_imagelist->Get(image_idx-1); NSImage *img=NULL; if (icon) img=(NSImage *)GetNSImageFromHICON(icon); [cell setStatusImage:img]; } } return [str autorelease]; } -(void)mouseDown:(NSEvent *)theEvent { if (([theEvent modifierFlags] & NSControlKeyMask) && IsRightClickEmulateEnabled()) { m_fakerightmouse=1; m_start_item=-1; m_start_subitem=-1; } else { if ([theEvent clickCount]>1 && SWELL_NeedModernListViewHacks()) { [super mouseDown:theEvent]; return; } m_leftmousemovecnt=0; m_fakerightmouse=0; NSPoint pt=[theEvent locationInWindow]; pt=[self convertPoint:pt fromView:nil]; m_start_item=(int)[self rowAtPoint:pt]; m_start_subitem=(int)[self columnAtPoint:pt]; m_start_item_clickmode=0; if (m_start_item >=0 && (m_fastClickMask&(1< 1 ? NM_DBLCLK : NM_CLICK; NMLISTVIEW nmlv={{(HWND)self,(UINT_PTR)[self tag], static_cast(msg)}, m_start_item, m_start_subitem, 0, 0, 0, {NSPOINT_TO_INTS(pt)}, }; SWELL_ListView_Row *row=m_items->Get(nmlv.iItem); if (row) nmlv.lParam = row->m_param; SendMessage((HWND)[self target],WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv); m_start_item_clickmode=4; } else { if (m_start_item>=0 && m_status_imagelist && LVSIL_STATE == m_status_imagelist_type && pt.x <= [self rowHeight]) // in left area { m_start_item_clickmode=1; } } } } -(void)mouseDragged:(NSEvent *)theEvent { if (++m_leftmousemovecnt==4) { if (m_start_item>=0 && !(m_start_item_clickmode&3)) { if (!m_lbMode) { // if m_start_item isnt selected, change selection to it now if (!(m_start_item_clickmode&4) && ![self isRowSelected:m_start_item]) { [self selectRowIndexes:[NSIndexSet indexSetWithIndex:m_start_item] byExtendingSelection:!!(GetAsyncKeyState(VK_CONTROL)&0x8000)]; } NMLISTVIEW hdr={{(HWND)self,(UINT_PTR)[self tag],LVN_BEGINDRAG},m_start_item,m_start_subitem,0,}; SendMessage((HWND)[self target],WM_NOTIFY,hdr.hdr.idFrom, (LPARAM) &hdr); m_start_item_clickmode |= 2; } } } else if (m_leftmousemovecnt > 4 && !(m_start_item_clickmode&1)) { HWND tgt=(HWND)[self target]; POINT p; GetCursorPos(&p); ScreenToClient(tgt,&p); SendMessage(tgt,WM_MOUSEMOVE,0,MAKELONG(p.x&0xffff,p.y)); } } -(void)mouseUp:(NSEvent *)theEvent { if ((m_fakerightmouse||([theEvent modifierFlags] & NSControlKeyMask)) && IsRightClickEmulateEnabled()) { [self rightMouseUp:theEvent]; } else { if ([theEvent clickCount]>1 && SWELL_NeedModernListViewHacks()) { [super mouseUp:theEvent]; return; } if (!(m_start_item_clickmode&1)) { if (m_leftmousemovecnt>=0 && m_leftmousemovecnt<4 && !(m_start_item_clickmode&4)) { const bool msel = [self allowsMultipleSelection]; if (m_lbMode && !msel) // listboxes --- allow clicking to reset the selection { [self deselectAll:self]; } if (SWELL_NeedModernListViewHacks()) { if (m_start_item>=0) { NSMutableIndexSet *m = [[NSMutableIndexSet alloc] init]; if (GetAsyncKeyState(VK_CONTROL)&0x8000) { [m addIndexes:[self selectedRowIndexes]]; if ([m containsIndex:m_start_item]) [m removeIndex:m_start_item]; else { if (!msel) [m removeAllIndexes]; [m addIndex:m_start_item]; } m_last_plainly_clicked_item = m_start_item; } else if (msel && (GetAsyncKeyState(VK_SHIFT)&0x8000)) { [m addIndexes:[self selectedRowIndexes]]; const int n = ListView_GetItemCount((HWND)self); if (m_last_plainly_clicked_item<0 || m_last_plainly_clicked_item>=n) m_last_plainly_clicked_item=m_start_item; if (m_last_shift_clicked_item>=0 && m_last_shift_clicked_item=4) { HWND tgt=(HWND)[self target]; POINT p; GetCursorPos(&p); ScreenToClient(tgt,&p); SendMessage(tgt,WM_LBUTTONUP,0,MAKELONG(p.x&0xffff,p.y)); } } } if (!m_lbMode && !(m_start_item_clickmode&(2|4))) { NSPoint pt=[theEvent locationInWindow]; pt=[self convertPoint:pt fromView:nil]; int col = (int)[self columnAtPoint:pt]; NMLISTVIEW nmlv={{(HWND)self,(UINT_PTR)[self tag], NM_CLICK}, (int)[self rowAtPoint:pt], col, 0, 0, 0, {NSPOINT_TO_INTS(pt)}, }; SWELL_ListView_Row *row=m_items->Get(nmlv.iItem); if (row) nmlv.lParam = row->m_param; SendMessage((HWND)[self target],WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv); } } - (void)rightMouseUp:(NSEvent *)theEvent { bool wantContext=true; if (!m_lbMode) { NSPoint pt=[theEvent locationInWindow]; pt=[self convertPoint:pt fromView:nil]; // note, windows selects on right mousedown NSInteger row =[self rowAtPoint:pt]; if (row >= 0 && ![self isRowSelected:row]) { NSIndexSet* rows=[NSIndexSet indexSetWithIndex:row]; [self deselectAll:self]; [self selectRowIndexes:rows byExtendingSelection:NO]; } NMLISTVIEW nmlv={{(HWND)self,(UINT_PTR)[self tag], NM_RCLICK}, (int)row, (int)[self columnAtPoint:pt], 0, 0, 0, {NSPOINT_TO_INTS(pt)}, }; if (SendMessage((HWND)[self target],WM_NOTIFY,nmlv.hdr.idFrom,(LPARAM)&nmlv)) wantContext=false; } if (wantContext) { POINT p; GetCursorPos(&p); SendMessage((HWND)[self target],WM_CONTEXTMENU,(WPARAM)self,MAKELONG(p.x&0xffff,p.y)); } m_fakerightmouse=0; } -(LRESULT)onSwellMessage:(UINT)msg p1:(WPARAM)wParam p2:(LPARAM)lParam { HWND hwnd=(HWND)self; switch (msg) { case LB_RESETCONTENT: ownermode_cnt=0; if (m_items) { m_items->Empty(true); [self reloadData]; } return 0; case LB_ADDSTRING: case LB_INSERTSTRING: { int cnt=ListView_GetItemCount(hwnd); if (msg == LB_ADDSTRING && (style & LBS_SORT)) { SWELL_ListView *tv=(SWELL_ListView*)hwnd; if (tv->m_lbMode && tv->m_items) { cnt=ptrlist_bsearch_mod((char *)lParam,tv->m_items,_listviewrowSearchFunc,NULL); } } if (msg==LB_ADDSTRING) wParam=cnt; else if (wParam > cnt) wParam=cnt; LVITEM lvi={LVIF_TEXT,(int)wParam,0,0,0,(char *)lParam}; ListView_InsertItem(hwnd,&lvi); } return wParam; case LB_GETCOUNT: return ListView_GetItemCount(hwnd); case LB_SETSEL: ListView_SetItemState(hwnd, (int)lParam,wParam ? LVIS_SELECTED : 0,LVIS_SELECTED); return 0; case LB_GETTEXT: if (lParam) { SWELL_ListView_Row *row=self->m_items ? self->m_items->Get(wParam) : NULL; *(char *)lParam = 0; const char *p; if (row && (p=row->get_col_txt(0))) { strcpy((char *)lParam, p); return (LRESULT)strlen(p); } } return LB_ERR; case LB_GETTEXTLEN: { SWELL_ListView_Row *row=self->m_items ? self->m_items->Get(wParam) : NULL; if (row) { const char *p=row->get_col_txt(0); return p?strlen(p):0; } } return LB_ERR; case LB_FINDSTRINGEXACT: if (lParam) { int x = (int) wParam + 1; if (x < 0) x=0; const int n = self->m_items ? self->m_items->GetSize() : 0; for (int i = 0; i < n; i ++) { SWELL_ListView_Row *row=self->m_items->Get(x); if (row) { const char *p = row->get_col_txt(0); if (p && !stricmp(p,(const char *)lParam)) return x; } if (++x >= n) x=0; } } return LB_ERR; case LB_GETSEL: return !!(ListView_GetItemState(hwnd,(int)wParam,LVIS_SELECTED)&LVIS_SELECTED); case LB_GETCURSEL: return [self selectedRow]; case LB_SETCURSEL: { if (wParamGet(wParam); if (row) return row->m_param; } } return 0; case LB_SETITEMDATA: { if (m_items) { SWELL_ListView_Row *row=m_items->Get(wParam); if (row) row->m_param=lParam; } } return 0; case LB_GETSELCOUNT: return [[self selectedRowIndexes] count]; case LB_DELETESTRING: ListView_DeleteItem((HWND)self, (int)wParam); return 0; case WM_SETREDRAW: if (wParam) { if (SWELL_GetOSXVersion() >= 0x1070 && [self respondsToSelector:@selector(endUpdates)]) { [self endUpdates]; // workaround for a weird 10.14.6 bug // if the caller calls this, then invalidaterect()s the parent window right away, // appkit will (sometimes) throw an exception unlocking focus on the NSScrollView. // invalidating our window directly seems to prevent this. [self setNeedsDisplay:YES]; } } else { if (SWELL_GetOSXVersion() >= 0x1070 && [self respondsToSelector:@selector(beginUpdates)]) [self beginUpdates]; } return 0; } return 0; } -(int)getSwellNotificationMode { return m_lbMode; } -(void)setSwellNotificationMode:(int)lbMode { m_lbMode=lbMode; } -(void)onSwellCommand:(int)cmd { // ignore commands } @end @implementation SWELL_ImageButtonCell - (NSRect)drawTitle:(NSAttributedString *)title withFrame:(NSRect)frame inView:(NSView *)controlView { return NSMakeRect(0,0,0,0); } @end @implementation SWELL_ODButtonCell - (BOOL)isTransparent { return YES; } - (BOOL)isOpaque { return NO; } - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { NSView *ctl=[self controlView]; if (!ctl) { [super drawWithFrame:cellFrame inView:controlView]; return; } HDC hdc=SWELL_CreateGfxContext([NSGraphicsContext currentContext]); if (hdc) { HWND notWnd = GetParent((HWND)ctl); DRAWITEMSTRUCT dis={ODT_BUTTON,(UINT)[ctl tag],0,0,0,(HWND)ctl,hdc,{0,},0}; NSRECT_TO_RECT(&dis.rcItem,cellFrame); SendMessage(notWnd,WM_DRAWITEM,dis.CtlID,(LPARAM)&dis); SWELL_DeleteGfxContext(hdc); } } @end @implementation SWELL_ODListViewCell -(void)setOwnerControl:(SWELL_ListView *)t { m_ownctl=t; m_lastidx=0; } -(void)setItemIdx:(int)idx { m_lastidx=idx; } - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { if (!m_ownctl) { [super drawInteriorWithFrame:cellFrame inView:controlView]; return; } int itemidx=m_lastidx; LPARAM itemData=0; SWELL_ListView_Row *row=m_ownctl->m_items->Get(itemidx); if (row) itemData=row->m_param; HDC hdc=SWELL_CreateGfxContext([NSGraphicsContext currentContext]); if (hdc) { HWND notWnd = GetParent((HWND)m_ownctl); DRAWITEMSTRUCT dis={ODT_LISTBOX,(UINT)[m_ownctl tag],(UINT)itemidx,0,0,(HWND)m_ownctl,hdc,{0,},(DWORD_PTR)itemData}; NSRECT_TO_RECT(&dis.rcItem,cellFrame); SendMessage(notWnd,WM_DRAWITEM,dis.CtlID,(LPARAM)&dis); SWELL_DeleteGfxContext(hdc); } } @end HWND GetDlgItem(HWND hwnd, int idx) { if (WDL_NOT_NORMALLY(!hwnd)) return 0; NSView *v=0; id pid=(id)hwnd; if ([pid isKindOfClass:[NSWindow class]]) v=[((NSWindow *)pid) contentView]; else if ([pid isKindOfClass:[NSView class]]) v=(NSView *)pid; if (!idx || !v) return (HWND)v; SWELL_BEGIN_TRY NSArray *ar = [v subviews]; const NSInteger n=[ar count]; for (NSInteger x=0;xhwnd && [rec->hwnd respondsToSelector:@selector(swellSetOwner:)]) [(SWELL_ModelessWindow *)rec->hwnd swellSetOwner:(id)bla]; rec=rec->_next; } } } } // move all child and owned windows over to new window NSArray *ar=[oldw childWindows]; if (ar) { int x; for (x = 0; x < [ar count]; x ++) { NSWindow *cw=[ar objectAtIndex:x]; if (cw) { [cw retain]; [oldw removeChildWindow:cw]; [(NSWindow *)bla addChildWindow:cw ordered:NSWindowAbove]; [cw release]; } } } if (oldpar) [oldpar addChildWindow:(NSWindow *)bla ordered:NSWindowAbove]; if (oldtitle[0]) SetWindowText(hwnd,oldtitle); [(NSWindow *)bla setFrame:fr display:dovis]; [(NSWindow *)bla setLevel:oldlevel]; if (dovis) ShowWindow(bla,SW_SHOW); DestroyWindow((HWND)oldw); } else { [oldw setContentView:tv]; [tv release]; } } } } return 0; } if (idx == GWL_HWNDPARENT) { NSWindow *window = [pid window]; if (![window respondsToSelector:@selector(swellGetOwner)]) return 0; NSWindow *new_owner = val && [(id)(INT_PTR)val isKindOfClass:[NSView class]] ? [(NSView *)(INT_PTR)val window] : NULL; if (new_owner && ![new_owner respondsToSelector:@selector(swellAddOwnedWindow:)]) new_owner=NULL; NSWindow *old_owner = [(SWELL_ModelessWindow *)window swellGetOwner]; if (old_owner != new_owner) { if (old_owner) [(SWELL_ModelessWindow*)old_owner swellRemoveOwnedWindow:window]; [(SWELL_ModelessWindow *)window swellSetOwner:nil]; if (new_owner) [(SWELL_ModelessWindow *)new_owner swellAddOwnedWindow:window]; } return (old_owner ? (LONG_PTR)[old_owner contentView] : 0); } if ([pid respondsToSelector:@selector(setSwellExtraData:value:)]) { WDL_ASSERT(idx>=0); // caller may be using a GWLP_* which is not yet implemented LONG_PTR ov=0; if ([pid respondsToSelector:@selector(getSwellExtraData:)]) ov=(LONG_PTR)[pid getSwellExtraData:idx]; [pid setSwellExtraData:idx value:val]; return ov; } WDL_ASSERT(false); // caller may be using a GWLP_* which is not yet implemented, or an extra index on a non-hwndchild SWELL_END_TRY(;) return 0; } LONG_PTR GetWindowLong(HWND hwnd, int idx) { if (WDL_NOT_NORMALLY(!hwnd)) return 0; id pid=(id)hwnd; SWELL_BEGIN_TRY if (idx==GWL_EXSTYLE && [pid respondsToSelector:@selector(swellGetExtendedStyle)]) { return (LONG_PTR)[pid swellGetExtendedStyle]; } if (idx==GWL_USERDATA && [pid respondsToSelector:@selector(getSwellUserData)]) { return (LONG_PTR)[pid getSwellUserData]; } if (idx==GWL_USERDATA && [pid isKindOfClass:[NSText class]]) { NSView *par = [pid superview]; if (par) { if (![par isKindOfClass:[SWELL_TextField class]]) par = [par superview]; if ([par isKindOfClass:[SWELL_TextField class]]) return [(SWELL_TextField*)par getSwellUserData]; } return 0xdeadf00b; } if (idx==GWL_ID && [pid respondsToSelector:@selector(tag)]) return [pid tag]; if (idx==GWL_WNDPROC && [pid respondsToSelector:@selector(getSwellWindowProc)]) { return (LONG_PTR)[pid getSwellWindowProc]; } if (idx==DWL_DLGPROC) { if ([pid respondsToSelector:@selector(getSwellDialogProc)]) { return (LONG_PTR)[pid getSwellDialogProc]; } return 0; // do not assert if GetWindowLongPtr DWLP_DLGPROC, used to query if something is a particular dialog } if (idx==GWL_STYLE) { int ret=0; if (![pid isHidden]) ret |= WS_VISIBLE; if ([pid respondsToSelector:@selector(getSwellStyle)]) { return (LONG_PTR)(([pid getSwellStyle]&~WS_VISIBLE) | ret); } if ([pid isKindOfClass:[NSButton class]]) { int tmp; if ([pid isKindOfClass:[NSPopUpButton class]]) ret |= CBS_DROPDOWNLIST; else if ([pid allowsMixedState]) ret |= BS_AUTO3STATE; else if ([pid isKindOfClass:[SWELL_Button class]] && (tmp = (int)[pid swellGetRadioFlags])) { if (tmp != 4096) { ret |= BS_AUTORADIOBUTTON; if (tmp&2) ret|=WS_GROUP; } } else ret |= BS_AUTOCHECKBOX; } if ([pid isKindOfClass:[NSView class]]) { if ([[pid window] contentView] != pid) ret |= WS_CHILDWINDOW; else { NSUInteger smask =[[pid window] styleMask]; if (smask & NSTitledWindowMask) { ret|=WS_CAPTION; if (smask & NSResizableWindowMask) ret|=WS_THICKFRAME; } } } return ret; } if (idx == GWL_HWNDPARENT) { NSWindow *window = [pid window]; if (![window respondsToSelector:@selector(swellGetOwner)]) return 0; NSWindow *old_owner = [(SWELL_ModelessWindow *)window swellGetOwner]; return (old_owner ? (LONG_PTR)[old_owner contentView] : 0); } if ([pid respondsToSelector:@selector(getSwellExtraData:)]) { WDL_ASSERT(idx>=0); // caller may be using a GWLP_* which is not yet implemented return (LONG_PTR)[pid getSwellExtraData:idx]; } WDL_ASSERT(false); // caller may be using a GWLP_* which is not yet implemented, or an extra index on a non-hwndchild SWELL_END_TRY(;) return 0; } static bool IsWindowImpl(NSView *ch, NSView *par) { if (!par || ![par isKindOfClass:[NSView class]]) return false; NSArray *ar = [par subviews]; if (!ar) return false; [ar retain]; NSInteger x,n=[ar count]; for (x=0;xtype != TYPE_BITMAP) return 0; return i->bitmapptr; } @implementation SWELL_Button : NSButton STANDARD_CONTROL_NEEDSDISPLAY_IMPL("Button") -(id) init { self = [super init]; if (self != nil) { m_userdata=0; m_swellGDIimage=0; m_radioflags=0; // =4096 if not a checkbox at all } return self; } -(int)swellGetRadioFlags { return m_radioflags; } -(void)swellSetRadioFlags:(int)f { m_radioflags=f; } -(LONG_PTR)getSwellUserData { return m_userdata; } -(void)setSwellUserData:(LONG_PTR)val { m_userdata=val; } -(void)setSwellGDIImage:(void *)par { m_swellGDIimage=par; } -(void *)getSwellGDIImage { return m_swellGDIimage; } @end NSFont *SWELL_GetNSFont(HGDIOBJ__ *obj); LRESULT SendMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (WDL_NOT_NORMALLY(!hwnd)) return 0; SWELL_BEGIN_TRY id obj=(id)hwnd; if ([obj respondsToSelector:@selector(onSwellMessage:p1:p2:)]) { return (LRESULT) [obj onSwellMessage:msg p1:wParam p2:lParam]; } else { if (msg == BM_GETCHECK && [obj isKindOfClass:[NSButton class]]) { NSInteger a=[(NSButton*)obj state]; if (a==NSMixedState) return BST_INDETERMINATE; return a!=NSOffState; } if (msg == BM_SETCHECK && [obj isKindOfClass:[NSButton class]]) { [(NSButton*)obj setState:(wParam&BST_INDETERMINATE)?NSMixedState:((wParam&BST_CHECKED)?NSOnState:NSOffState)]; return 0; } if ((msg==BM_GETIMAGE || msg == BM_SETIMAGE) && [obj isKindOfClass:[SWELL_Button class]]) { if (wParam != IMAGE_BITMAP && wParam != IMAGE_ICON) return 0; // ignore unknown types LONG_PTR ret=(LONG_PTR) (void *)[obj getSwellGDIImage]; if (msg==BM_SETIMAGE) { NSImage *img=NULL; if (lParam) img=(NSImage *)__GetNSImageFromHICON((HICON)lParam); [obj setImage:img]; [obj setSwellGDIImage:(void *)(img?lParam:0)]; } return ret; } else if (msg >= CB_ADDSTRING && msg <= CB_INITSTORAGE && ([obj isKindOfClass:[NSPopUpButton class]] || [obj isKindOfClass:[NSComboBox class]])) { switch (msg) { case CB_ADDSTRING: return SWELL_CB_AddString(hwnd,0,(char*)lParam); case CB_DELETESTRING: SWELL_CB_DeleteString(hwnd,0,(int)wParam); return 1; case CB_GETCOUNT: return SWELL_CB_GetNumItems(hwnd,0); case CB_GETCURSEL: return SWELL_CB_GetCurSel(hwnd,0); case CB_GETLBTEXT: return SWELL_CB_GetItemText(hwnd,0,(int)wParam,(char *)lParam, 1<<20); case CB_GETLBTEXTLEN: return SWELL_CB_GetItemText(hwnd,0,(int)wParam,NULL,0); case CB_INSERTSTRING: return SWELL_CB_InsertString(hwnd,0,(int)wParam,(char *)lParam); case CB_RESETCONTENT: SWELL_CB_Empty(hwnd,0); return 0; case CB_SETCURSEL: SWELL_CB_SetCurSel(hwnd,0,(int)wParam); return 0; case CB_GETITEMDATA: return SWELL_CB_GetItemData(hwnd,0,(int)wParam); case CB_SETITEMDATA: SWELL_CB_SetItemData(hwnd,0,(int)wParam,lParam); return 0; case CB_FINDSTRING: case CB_FINDSTRINGEXACT: if (lParam) return SWELL_CB_FindString(hwnd,0,(int)wParam,(const char *)lParam,msg==CB_FINDSTRINGEXACT); return CB_ERR; case CB_INITSTORAGE: return 0; } return 0; } else if (msg >= TBM_GETPOS && msg <= TBM_SETRANGE && ([obj isKindOfClass:[NSSlider class]])) { switch (msg) { case TBM_GETPOS: return SWELL_TB_GetPos(hwnd,0); case TBM_SETTIC: SWELL_TB_SetTic(hwnd,0,(int)lParam); return 1; case TBM_SETPOS: SWELL_TB_SetPos(hwnd,0,(int)lParam); return 1; case TBM_SETRANGE: SWELL_TB_SetRange(hwnd,0,LOWORD(lParam),HIWORD(lParam)); return 1; } return 0; } else if ((msg == EM_SETSEL || msg == EM_GETSEL || msg == EM_SETPASSWORDCHAR) && ([obj isKindOfClass:[NSTextField class]])) { if (msg == EM_GETSEL) { NSRange range={0,}; NSResponder *rs = [[obj window] firstResponder]; if ([rs isKindOfClass:[NSView class]] && [(NSView *)rs isDescendantOf:obj]) { NSText* text=[[obj window] fieldEditor:YES forObject:(NSTextField*)obj]; if (text) range=[text selectedRange]; } if (wParam) *(int*)wParam=(int)range.location; if (lParam) *(int*)lParam=(int)(range.location+range.length); } else if (msg == EM_SETSEL) { // [(NSTextField*)obj selectText:obj]; // Force the window's text field editor onto this control // don't force it, just ignore EM_GETSEL/EM_SETSEL if not in focus NSResponder *rs = [[obj window] firstResponder]; if ([rs isKindOfClass:[NSView class]] && [(NSView *)rs isDescendantOf:obj]) { NSText* text = [[obj window] fieldEditor:YES forObject:(NSTextField*)obj]; // then get it from the window NSUInteger sl = [[text string] length]; if (wParam == -1) lParam = wParam = 0; else if (lParam == -1) lParam = sl; if (wParam>sl) wParam=sl; if (lParam>sl) lParam=sl; if (text) [text setSelectedRange:NSMakeRange(wParam, wdl_max(lParam-wParam,0))]; // and set the range } } else if (msg == EM_SETPASSWORDCHAR) { // not implemented, because it requires replacing obj within its parent window // instead caller explicitly destroy the edit control and create a new one with ES_PASSWORD } return 0; } else if (msg == WM_SETFONT && ([obj isKindOfClass:[NSTextField class]] || [obj isKindOfClass:[NSTextView class]])) { NSFont *font = SWELL_GetNSFont((HGDIOBJ__*)wParam); if (font) [obj setFont:font]; return 0; } else if (msg == WM_SETFONT && ([obj isKindOfClass:[NSScrollView class]])) { NSView *cv=[(NSScrollView *)obj documentView]; if (cv && [cv isKindOfClass:[NSTextView class]]) { NSFont *font = SWELL_GetNSFont((HGDIOBJ__*)wParam); if (font) [(NSTextView *)cv setFont:font]; return 0; } } else { NSWindow *w; NSView *v; // if content view gets unhandled message send to window if ([obj isKindOfClass:[NSView class]] && (w=[obj window]) && [w contentView] == obj && [w respondsToSelector:@selector(onSwellMessage:p1:p2:)]) { return (LRESULT) [(SWELL_hwndChild *)w onSwellMessage:msg p1:wParam p2:lParam]; } // if window gets unhandled message send to content view else if ([obj isKindOfClass:[NSWindow class]] && (v=[obj contentView]) && [v respondsToSelector:@selector(onSwellMessage:p1:p2:)]) { return (LRESULT) [(SWELL_hwndChild *)v onSwellMessage:msg p1:wParam p2:lParam]; } } } SWELL_END_TRY(;) return 0; } static NSView *NavigateUpScrollClipViews(NSView *ch); void DestroyWindow(HWND hwnd) { if (WDL_NOT_NORMALLY(!hwnd)) return; SWELL_BEGIN_TRY id pid=(id)hwnd; if ([pid isKindOfClass:[NSView class]]) { KillTimer(hwnd,~(UINT_PTR)0); sendSwellMessage((id)pid,WM_DESTROY,0,0); pid = NavigateUpScrollClipViews(pid); NSWindow *pw = [(NSView *)pid window]; if (pw && [pw contentView] == pid) // destroying contentview should destroy top level window { DestroyWindow((HWND)pw); } else { if (pw) { id foc=[pw firstResponder]; if (foc && (foc == pid || IsChild((HWND)pid,(HWND)foc))) { [pw makeFirstResponder:nil]; } } [(NSView *)pid removeFromSuperview]; } } else if ([pid isKindOfClass:[NSWindow class]]) { KillTimer(hwnd,~(UINT_PTR)0); sendSwellMessage([(id)pid contentView],WM_DESTROY,0,0); sendSwellMessage((id)pid,WM_DESTROY,0,0); if ([(id)pid respondsToSelector:@selector(swellDoDestroyStuff)]) [(id)pid swellDoDestroyStuff]; NSWindow *par=[(NSWindow*)pid parentWindow]; if (par) { [par removeChildWindow:(NSWindow*)pid]; } [(NSWindow *)pid close]; // this is probably bad, but close takes too long to close! } SWELL_END_TRY(;) } void EnableWindow(HWND hwnd, int enable) { if (WDL_NOT_NORMALLY(!hwnd)) return; SWELL_BEGIN_TRY id bla=(id)hwnd; if ([bla isKindOfClass:[NSWindow class]]) bla = [bla contentView]; if (bla && [bla respondsToSelector:@selector(setEnabled:)]) { if (!enable) { HWND foc = GetFocus(); if (foc && (foc==hwnd || IsChild(hwnd,foc))) { HWND par = GetParent(hwnd); if (par) SetFocus(par); } } if (enable == -1000 && [bla respondsToSelector:@selector(setEnabledSwellNoFocus)]) [(SWELL_hwndChild *)bla setEnabledSwellNoFocus]; else [bla setEnabled:(enable?YES:NO)]; if ([bla isKindOfClass:[SWELL_TextField class]]) [(SWELL_TextField*)bla initColors:-1]; } SWELL_END_TRY(;) } void SetForegroundWindow(HWND hwnd) { WDL_ASSERT(hwnd != NULL); SetFocus(hwnd); } void SetFocus(HWND hwnd) // these take NSWindow/NSView, and return NSView * { id r=(id) hwnd; if (!r) return; // on win32 SetFocus(NULL) is allowed, removes focus (maybe we should implement) SWELL_BEGIN_TRY if ([r isKindOfClass:[NSWindow class]]) { [(NSWindow *)r makeFirstResponder:[(NSWindow *)r contentView]]; if ([(NSWindow *)r isVisible]) [(NSWindow *)r makeKeyAndOrderFront:nil]; } else if (WDL_NORMALLY([r isKindOfClass:[NSView class]])) { NSWindow *wnd=[(NSView *)r window]; if (wnd) { if ([wnd isVisible]) [wnd makeKeyAndOrderFront:nil]; if ([r acceptsFirstResponder]) [wnd makeFirstResponder:r]; } } SWELL_END_TRY(;) } void SWELL_GetViewPort(RECT *r, const RECT *sourcerect, bool wantWork) { SWELL_BEGIN_TRY NSArray *ar=[NSScreen screens]; const NSInteger cnt=[ar count]; if (!sourcerect || cnt < 2) { NSScreen *sc=[NSScreen mainScreen]; if (sc) { NSRect tr=wantWork ? [sc visibleFrame] : [sc frame]; NSRECT_TO_RECT(r,tr); } else { r->left=r->top=0; r->right=1600; r->bottom=1200; } } else { double best_score = -1e20; // find screen of best intersection RECT sr = *sourcerect; if (sr.top > sr.bottom) { sr.top = sourcerect->bottom; sr.bottom = sourcerect->top; } if (sr.left > sr.right) { sr.left = sourcerect->right; sr.right = sourcerect->left; } for (NSInteger x = 0; x < cnt; x ++) { NSScreen *sc=[ar objectAtIndex:x]; if (sc) { NSRect tr=wantWork ? [sc visibleFrame] : [sc frame]; RECT tmp; NSRECT_TO_RECT(&tmp,tr); double score; RECT res; if (IntersectRect(&res, &tmp, &sr)) { score = wdl_abs((res.right-res.left) * (res.bottom-res.top)); } else { int dx = 0, dy = 0; if (tmp.left > sr.right) dx = tmp.left - sr.right; else if (tmp.right < sr.left) dx = sr.left - tmp.right; if (tmp.bottom < sr.top) dy = tmp.bottom - sr.top; else if (tmp.top > sr.bottom) dy = tmp.top - sr.bottom; score = - (dx*dx + dy*dy); } if (!x || score > best_score) { best_score = score; *r = tmp; } } } } SWELL_END_TRY(;) } void ScreenToClient(HWND hwnd, POINT *p) { if (WDL_NOT_NORMALLY(!hwnd)) return; // no need to try/catch, this should never have an issue *wince* id ch=(id)hwnd; if ([ch isKindOfClass:[NSWindow class]]) ch=[((NSWindow *)ch) contentView]; if (WDL_NOT_NORMALLY(!ch || ![ch isKindOfClass:[NSView class]])) return; NSWindow *window=[ch window]; NSPoint wndpt = [window convertScreenToBase:NSMakePoint(p->x,p->y)]; // todo : WM_NCCALCSIZE NSPOINT_TO_POINT(p, [ch convertPoint:wndpt fromView:nil]); } void ClientToScreen(HWND hwnd, POINT *p) { if (WDL_NOT_NORMALLY(!hwnd)) return; id ch=(id)hwnd; if ([ch isKindOfClass:[NSWindow class]]) ch=[((NSWindow *)ch) contentView]; if (!ch || ![ch isKindOfClass:[NSView class]]) return; NSWindow *window=[ch window]; NSPoint wndpt = [ch convertPoint:NSMakePoint(p->x,p->y) toView:nil]; // todo : WM_NCCALCSIZE NSPOINT_TO_POINT(p,[window convertBaseToScreen:wndpt]); } static NSView *NavigateUpScrollClipViews(NSView *ch) { NSView *par=[ch superview]; if (par && [par isKindOfClass:[NSClipView class]]) { par=[par superview]; if (par && [par isKindOfClass:[NSScrollView class]]) { ch=par; } } return ch; } HWND SWELL_NavigateUpScrollClipViews(HWND h) { NSView *v = 0; if (h && [(id)h isKindOfClass:[NSView class]]) v = (NSView *)h; else if (h && [(id)h isKindOfClass:[NSWindow class]]) v = [(NSWindow *)h contentView]; if (v) return (HWND)NavigateUpScrollClipViews(v); return 0; } bool GetWindowRect(HWND hwnd, RECT *r) { r->left=r->top=r->right=r->bottom=0; if (WDL_NOT_NORMALLY(!hwnd)) return false; SWELL_BEGIN_TRY id ch=(id)hwnd; NSWindow *nswnd; if ([ch isKindOfClass:[NSView class]] && (nswnd=[(NSView *)ch window]) && [nswnd contentView]==ch) ch=nswnd; if ([ch isKindOfClass:[NSWindow class]]) { NSRECT_TO_RECT(r,[ch frame]); return true; } if (![ch isKindOfClass:[NSView class]]) return false; ch=NavigateUpScrollClipViews(ch); NSRECT_TO_RECT(r,[ch bounds]); ClientToScreen((HWND)ch,(POINT *)r); ClientToScreen((HWND)ch,((POINT *)r)+1); SWELL_END_TRY(return false;) return true; } void GetWindowContentViewRect(HWND hwnd, RECT *r) { SWELL_BEGIN_TRY NSWindow *nswnd; if (hwnd && [(id)hwnd isKindOfClass:[NSView class]] && (nswnd=[(NSView *)hwnd window]) && [nswnd contentView]==(id)hwnd) hwnd=(HWND)nswnd; if (hwnd && [(id)hwnd isKindOfClass:[NSWindow class]]) { NSView *ch=[(id)hwnd contentView]; NSRECT_TO_RECT(r,[ch bounds]); ClientToScreen(hwnd,(POINT *)r); ClientToScreen(hwnd,((POINT *)r)+1); } else GetWindowRect(hwnd,r); SWELL_END_TRY(;) } void GetClientRect(HWND hwnd, RECT *r) { r->left=r->top=r->right=r->bottom=0; if (WDL_NOT_NORMALLY(!hwnd)) return; SWELL_BEGIN_TRY id ch=(id)hwnd; if ([ch isKindOfClass:[NSWindow class]]) ch=[((NSWindow *)ch) contentView]; if (!ch || ![ch isKindOfClass:[NSView class]]) return; ch=NavigateUpScrollClipViews(ch); NSRECT_TO_RECT(r,[ch bounds]); // todo this may need more attention NCCALCSIZE_PARAMS tr={{*r,},}; SendMessage(hwnd,WM_NCCALCSIZE,FALSE,(LPARAM)&tr); r->right = r->left + (tr.rgrc[0].right-tr.rgrc[0].left); r->bottom = r->top + (tr.rgrc[0].bottom-tr.rgrc[0].top); SWELL_END_TRY(;) } void SetWindowPos(HWND hwnd, HWND hwndAfter, int x, int y, int cx, int cy, int flags) { if (WDL_NOT_NORMALLY(!hwnd)) return; SWELL_BEGIN_TRY NSWindow *nswnd; // content views = move window if (hwnd && [(id)hwnd isKindOfClass:[NSView class]] && (nswnd=[(NSView *)hwnd window]) && [nswnd contentView]==(id)hwnd) hwnd=(HWND)nswnd; // todo: handle SWP_SHOWWINDOW id ch=(id)hwnd; bool isview=false; if ([ch isKindOfClass:[NSWindow class]] || (isview=[ch isKindOfClass:[NSView class]])) { if (isview) { ch=NavigateUpScrollClipViews(ch); if (isview && !(flags&SWP_NOZORDER)) { NSView *v = (NSView *)ch; NSView *par = [v superview]; NSArray *subs = [par subviews]; NSInteger idx = [subs indexOfObjectIdenticalTo:v], cnt=[subs count]; NSView *viewafter = NULL; NSWindowOrderingMode omode = NSWindowAbove; if (cnt>1 && hwndAfter != (HWND)ch) { if (hwndAfter==HWND_TOPMOST||hwndAfter==HWND_NOTOPMOST) { } else if (hwndAfter == HWND_TOP) { if (idx0) viewafter = [subs objectAtIndex:0]; omode = NSWindowBelow; } else { NSInteger a=[subs indexOfObjectIdenticalTo:(NSView *)hwndAfter]; if (a != NSNotFound && a != (idx-1)) viewafter = (NSView *)hwndAfter; } } if (viewafter) { HWND h = GetCapture(); if (!h || (h!=(HWND)v && !IsChild((HWND)v,h))) // if this window is captured don't reorder! { NSView *oldfoc = (NSView*)[[v window] firstResponder]; if (!oldfoc || ![oldfoc isKindOfClass:[NSView class]] || (oldfoc != v && ![oldfoc isDescendantOf:v])) oldfoc=NULL; // better way to do this? maybe :/ [v retain]; [v removeFromSuperviewWithoutNeedingDisplay]; [par addSubview:v positioned:omode relativeTo:viewafter]; if (oldfoc && [oldfoc isKindOfClass:[NSView class]] && [oldfoc window] == [v window]) [[v window] makeFirstResponder:oldfoc]; [v release]; } } } } NSRect f=[ch frame]; bool repos=false; if (!(flags&SWP_NOMOVE)) { f.origin.x=(float)x; f.origin.y=(float)y; repos=true; } if (!(flags&SWP_NOSIZE)) { f.size.width=(float)cx; f.size.height=(float)cy; if (f.size.height<0)f.size.height=-f.size.height; repos=true; } if (repos) { if (!isview) { NSSize mins=[ch minSize]; NSSize maxs=[ch maxSize]; if (f.size.width < mins.width) f.size.width=mins.width; else if (f.size.width > maxs.width) f.size.width=maxs.width; if (f.size.height < mins.height) f.size.height=mins.height; else if (f.size.height> maxs.height) f.size.height=maxs.height; [ch setFrame:f display:NO]; [ch display]; } else { // this doesnt seem to actually be a good idea anymore // if ([[ch window] contentView] != ch && ![[ch superview] isFlipped]) // f.origin.y -= f.size.height; [ch setFrame:f]; if ([ch isKindOfClass:[NSScrollView class]]) { NSView *cv=[ch documentView]; if (cv && [cv isKindOfClass:[NSTextView class]]) { NSRect fr=[cv frame]; NSSize sz=[ch contentSize]; int a=0; if (![ch hasHorizontalScroller]) {a ++; fr.size.width=sz.width; } if (![ch hasVerticalScroller]) { a++; fr.size.height=sz.height; } if (a) [cv setFrame:fr]; } } } } return; } SWELL_END_TRY(;) } BOOL EnumWindows(BOOL (*proc)(HWND, LPARAM), LPARAM lp) { NSArray *ch=[NSApp windows]; [ch retain]; const NSInteger n=[ch count]; for(NSInteger x=0;x0) { return (HWND)[ar objectAtIndex:0]; } return 0; } if (what == GW_OWNER) { v=NavigateUpScrollClipViews(v); if ([[v window] contentView] == v) { if ([[v window] respondsToSelector:@selector(swellGetOwner)]) { return (HWND)[(SWELL_ModelessWindow*)[v window] swellGetOwner]; } return 0; } return (HWND)[v superview]; } if (what >= GW_HWNDFIRST && what <= GW_HWNDPREV) { v=NavigateUpScrollClipViews(v); if ([[v window] contentView] == v) { if (what <= GW_HWNDLAST) return (HWND)hwnd; // content view is only window return 0; // we're the content view so cant do next/prev } NSView *par=[v superview]; if (par) { NSArray *ar=[par subviews]; NSInteger cnt; if (ar && (cnt=[ar count]) > 0) { if (what == GW_HWNDFIRST) return (HWND)[ar objectAtIndex:0]; if (what == GW_HWNDLAST) return (HWND)[ar objectAtIndex:(cnt-1)]; NSInteger idx=[ar indexOfObjectIdenticalTo:v]; if (idx == NSNotFound) return 0; if (what==GW_HWNDNEXT) idx++; else if (what==GW_HWNDPREV) idx--; if (idx<0 || idx>=cnt) return 0; return (HWND)[ar objectAtIndex:idx]; } } return 0; } SWELL_END_TRY(;) return 0; } HWND GetParent(HWND hwnd) { SWELL_BEGIN_TRY if (WDL_NORMALLY(hwnd) && [(id)hwnd isKindOfClass:[NSView class]]) { hwnd=(HWND)NavigateUpScrollClipViews((NSView *)hwnd); NSView *cv=[[(NSView *)hwnd window] contentView]; if (cv == (NSView *)hwnd) hwnd=(HWND)[(NSView *)hwnd window]; // passthrough to get window parent else { HWND h=(HWND)[(NSView *)hwnd superview]; return h; } } if (hwnd && [(id)hwnd isKindOfClass:[NSWindow class]]) { HWND h= (HWND)[(NSWindow *)hwnd parentWindow]; if (h) h=(HWND)[(NSWindow *)h contentView]; if (h) return h; } if (hwnd && [(id)hwnd respondsToSelector:@selector(swellGetOwner)]) { HWND h= (HWND)[(SWELL_ModelessWindow *)hwnd swellGetOwner]; if (h && [(id)h isKindOfClass:[NSWindow class]]) h=(HWND)[(NSWindow *)h contentView]; return h; } SWELL_END_TRY(;) return 0; } HWND SetParent(HWND hwnd, HWND newPar) { SWELL_BEGIN_TRY NSView *v=(NSView *)hwnd; WDL_ASSERT(hwnd != NULL); if (!v || ![(id)v isKindOfClass:[NSView class]]) return 0; v=NavigateUpScrollClipViews(v); if ([(id)hwnd isKindOfClass:[NSView class]]) { NSView *tv=(NSView *)hwnd; if ([[tv window] contentView] == tv) // if we're reparenting a contentview (aka top level window) { if (!newPar) return NULL; NSView *npv = (NSView *)newPar; if ([npv isKindOfClass:[NSWindow class]]) npv=[(NSWindow *)npv contentView]; if (!npv || ![npv isKindOfClass:[NSView class]]) return NULL; char oldtitle[2048]; oldtitle[0]=0; GetWindowText(hwnd,oldtitle,sizeof(oldtitle)); NSWindow *oldwnd = [tv window]; id oldown = NULL; if ([oldwnd respondsToSelector:@selector(swellGetOwner)]) oldown=[(SWELL_ModelessWindow*)oldwnd swellGetOwner]; if ([tv isKindOfClass:[SWELL_hwndChild class]]) ((SWELL_hwndChild*)tv)->m_lastTopLevelOwner = oldown; [tv retain]; SWELL_hwndChild *tmpview = [[SWELL_hwndChild alloc] initChild:nil Parent:(NSView *)oldwnd dlgProc:nil Param:0]; [tmpview release]; [npv addSubview:tv]; [tv release]; DestroyWindow((HWND)oldwnd); // close old window since its no longer used if (oldtitle[0]) SetWindowText(hwnd,oldtitle); return (HWND)npv; } else if (!newPar) // not content view, not parent (so making it a top level modeless dialog) { char oldtitle[2048]; oldtitle[0]=0; GetWindowText(hwnd,oldtitle,sizeof(oldtitle)); [tv retain]; [tv removeFromSuperview]; unsigned int wf=(NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask|NSResizableWindowMask); if ([tv respondsToSelector:@selector(swellCreateWindowFlags)]) wf=(unsigned int)[(SWELL_hwndChild *)tv swellCreateWindowFlags]; HWND newOwner=NULL; if ([tv isKindOfClass:[SWELL_hwndChild class]]) { id oldown = ((SWELL_hwndChild*)tv)->m_lastTopLevelOwner; if (oldown) { NSArray *ch=[NSApp windows]; const NSInteger n = [ch count]; for(NSInteger x=0;x