• R/O
  • SSH
  • HTTPS

molby:


File Info

Rev. 507
Size 16,590 bytes
Time 2014-03-25 09:51:21
Author toshinagata1964
Log Message

Table view on Mac sometimes crashes. Hopefully fixed (I am not sure)

Content

/*
 *  MyListCtrl.cpp
 *  Molby
 *
 *  Created by Toshi Nagata on 08/12/09.
 *  Copyright 2008 Toshi Nagata. All rights reserved.
 *
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation version 2 of the License.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 */

#include "MyListCtrl.h"
#include "MyMBConv.h"

#include "wx/dcclient.h"
#include "wx/scrolwin.h"
#include "wx/glcanvas.h"
#include "wx/menu.h"

const wxEventType MyListCtrlEvent = wxNewEventType();

IMPLEMENT_DYNAMIC_CLASS(MyListCtrl, wxGenericListCtrl)

BEGIN_EVENT_TABLE(MyListCtrl, wxGenericListCtrl)
EVT_LIST_ITEM_SELECTED(-1, MyListCtrl::OnItemSelectionChanged)
EVT_LIST_ITEM_DESELECTED(-1, MyListCtrl::OnItemSelectionChanged)
EVT_COMMAND(MyListCtrlEvent_tableSelectionChanged, MyListCtrlEvent, MyListCtrl::OnTableSelectionChanged)
EVT_COMMAND(MyListCtrlEvent_enableTableSelectionNotification, MyListCtrlEvent, MyListCtrl::OnEnableTableSelectionNotification)
EVT_LIST_BEGIN_DRAG(-1, MyListCtrl::OnBeginDrag)
EVT_LEFT_DCLICK(MyListCtrl::OnLeftDClick)
EVT_CHAR(MyListCtrl::OnChar)
EVT_LEFT_DOWN(MyListCtrl::OnMouseDown)
END_EVENT_TABLE()

MyListCtrl::MyListCtrl()
{
	editText = NULL;
#if defined(__WXMAC__)
	//  On OSX, the default font seems to be 14-point, which is too big.
	wxFont font = this->GetFont();
	font.SetPointSize(12);
	this->SetFont(font);
#endif
}

MyListCtrl::~MyListCtrl()
{
	if (editText != NULL) {
	/*	editText->Destroy(); */  /*  May be unnecessary  */
		editText = NULL;
	}
}

bool
MyListCtrl::Create(wxWindow* parent, wxWindowID wid, const wxPoint& pos, const wxSize& size)
{
	this->wxGenericListCtrl::Create(parent, wid, pos, size, wxLC_REPORT | wxLC_VIRTUAL | wxBORDER_SIMPLE);
	dataSource = NULL;
	editText = NULL;
	selectionChangeNotificationSent = false;
	selectionChangeNotificationEnabled = true;
	subTitleRowAttr = new wxListItemAttr;
	dragTargetRow = -1;
	return true;
}

void
MyListCtrl::SetDataSource(MyListCtrlDataSource *source)
{
	dataSource = source;
	RefreshTable();
}

void
MyListCtrl::RefreshTable()
{
	if (dataSource != NULL) {
		int nrows = dataSource->GetItemCount(this);
		SetItemCount(nrows);
		if (nrows > 0) {
			RefreshItems(0, nrows - 1);
		}
	}
}

// Define the repainting behaviour
void
MyListCtrl::OnPaintCallback(wxDC *dc)
{
	if (dragTargetRow >= 0) {
		wxRect r;
		wxPen pen = *wxCYAN_PEN;
		int dx, dy, y;
#if defined(__WXMSW__)
		static const int offset_y = -6;
#else
		static const int offset_y = 0;
#endif
		CalcScrolledPosition(0, 0, &dx, &dy);	
		pen.SetWidth(3);
		dc->SetPen(pen);
		if (dragTargetRow == GetItemCount()) {
			GetItemRect(dragTargetRow - 1, r);
			y = r.y - dy;
		} else {
			GetItemRect(dragTargetRow, r);
			y = r.y - dy - r.height;
		}
		y += offset_y;
	//	printf("dragTargetRow = %d, r.y = %d, y = %d, r.x-dx = %d, r.width = %d\n", dragTargetRow, r.y, y, r.x - dx, r.width);
		dc->DrawLine(r.x - dx, y, r.x - dx + r.width, y);
	}
}

//  Callback function that is called from wxListMainWindow::OnPaint().
//  This is a very ugly hack, but I can think of no alternative...
void
wxListCtrl_onPaintCallback(wxGenericListCtrl *listctrl, wxDC *dc)
{
	MyListCtrl *ctrl = wxStaticCast(listctrl, MyListCtrl);
	if (ctrl != NULL && dc != NULL)
		ctrl->OnPaintCallback(dc);
}

wxString
MyListCtrl::OnGetItemText(long item, long column) const
{
	if (dataSource == NULL)
		return wxEmptyString;
	return dataSource->GetItemText((MyListCtrl *)this, item, column);
}

wxListItemAttr *
MyListCtrl::OnGetItemAttr(long item) const
{
	float fg[3], bg[3];
	long row, col;
	int ret;
	row = item % 1000000;
	col = item / 1000000 - 1;  //  -1 for all rows
	ret = dataSource->SetItemColor((MyListCtrl *)this, row, col, fg, bg);
	if (ret == 0)
		return NULL;
	if (ret & 1) {
		wxColour fgcol((int)(fg[0] * 255), (int)(fg[1] * 255), (int)(fg[2] * 255));
		subTitleRowAttr->SetTextColour(fgcol);
	} else subTitleRowAttr->SetTextColour(*wxBLACK);
	if (ret & 2) {
		wxColour bgcol((int)(bg[0] * 255), (int)(bg[1] * 255), (int)(bg[2] * 255));
		subTitleRowAttr->SetBackgroundColour(bgcol);
	} else subTitleRowAttr->SetBackgroundColour(*wxWHITE);
	return subTitleRowAttr;
}

void
MyListCtrl::PostSelectionChangeNotification()
{
	if (!selectionChangeNotificationSent && selectionChangeNotificationEnabled) {
		selectionChangeNotificationSent = true;
		wxCommandEvent myEvent(MyListCtrlEvent, MyListCtrlEvent_tableSelectionChanged);
		wxPostEvent(this, myEvent);
	}
}

void
MyListCtrl::OnBeginLabelEdit(wxListEvent &event)
{
//	printf("OnBeginLabelEdit: item index = %d\n", event.GetIndex());
}

void
MyListCtrl::OnEndLabelEdit(wxListEvent &event)
{
//	printf("OnEndLabelEdit: item index = %d\n", event.GetIndex());
}

void
MyListCtrl::OnItemActivated(wxListEvent &event)
{
//	printf("OnItemActivated: item index = %d, col = %d\n", event.GetIndex(), event.GetItem().m_col);
}

void
MyListCtrl::OnBeginDrag(wxListEvent &event)
{
	int count = GetItemCount();
	
	if (dataSource == NULL || !dataSource->IsDragAndDropEnabled(this))
		return;
	EndEditText(true);
	dragTargetRow = -1;
	while (1) {
		wxRect r;
		wxMouseState mstate = wxGetMouseState();
		wxPoint pt(mstate.GetX(), mstate.GetY());
		pt = ScreenToClient(pt);
		long newRow = FindItem(-1, pt, 0);
		if (newRow != dragTargetRow) {
			if (newRow >= 0)
				EnsureVisible(newRow);
			else {
				GetItemRect(0, r);
				if (pt.y < r.y)
					EnsureVisible(0);
				else {
					GetItemRect(count - 1, r);
					if (pt.y >= r.y) {
						EnsureVisible(count - 1);
						if (pt.y < r.y + r.height)
							newRow = count;
					} else if (count > 0 && pt.y >= r.y - r.height)
						newRow = count - 1;
				}
			}
		}
		if (newRow != dragTargetRow) {
			if (newRow >= 0) {
				if (newRow == count) {
					GetItemRect(newRow - 1, r);
					r.y += r.height / 2;
				} else {
					GetItemRect(newRow, r);
					r.y -= r.height / 2;
				}
				RefreshRect(r);
			}
			if (dragTargetRow >= 0) {
				if (dragTargetRow == count) {
					GetItemRect(dragTargetRow - 1, r);
					r.y += r.height / 2;
				} else {
					GetItemRect(dragTargetRow, r);
					r.y -= r.height / 2;
				}
				RefreshRect(r);
			}
			dragTargetRow = newRow;
			Update();
		}
		if (!mstate.LeftIsDown()) {
			//  If the mouse cursor is outside the item rect, then dragging should be discarded
			if (dragTargetRow >= 0) {
				r = GetClientRect();
				if (!r.Contains(pt))
					dragTargetRow = -1;
			}
			break;
		}
	}
	if (dragTargetRow >= 0)
		dataSource->DragSelectionToRow(this, dragTargetRow);
	dragTargetRow = -1;
	Update();
}

bool
MyListCtrl::GetItemRectForRowAndColumn(wxRect &rect, int row, int column)
{
	int i, xpos, width, xunit, yunit;
	if (!GetItemRect(row, rect))
		return false;
	GetScrollPixelsPerUnit(&xunit, &yunit);
	xpos = -GetScrollPos(wxHORIZONTAL) * xunit;
	for (i = 0; i < column; i++) {
		width = GetColumnWidth(i);
		xpos += width;
	}
	rect.SetX(xpos);
	rect.SetWidth(GetColumnWidth(column));
	return true;
}

void
MyListCtrl::GetScrollPixelsPerUnit(int *xunit, int *yunit)
{
	wxGenericListCtrl::GetScrollPixelsPerUnit(xunit, yunit);
//	int x, y;
//	/*  m_mainWin is a protected member in wxGenericListCtrl  */
//	/*((wxScrolledWindow *)m_mainWin)->*/GetScrollPixelsPerUnit(&x, &y);	
//	if (xunit != NULL)
//		*xunit = x;
//	if (yunit != NULL)
//		*yunit = y;
}

bool
MyListCtrl::FindItemAtPosition(const wxPoint &pos, int *row, int *column)
{
	int i, r, ncols, width, xpos, flags, xunit, yunit;
	r = (int)HitTest(pos, flags, NULL);
	if (r == wxNOT_FOUND)
		return false;
	ncols = GetColumnCount();
	GetScrollPixelsPerUnit(&xunit, &yunit);
	xpos = -GetScrollPos(wxHORIZONTAL) * xunit;
	for (i = 0; i < ncols; i++) {
		width = GetColumnWidth(i);
		xpos += width;
		if (pos.x < xpos)
			break;
	}
	if (i >= ncols)
		return false;
	*row = r;
	*column = i;
	return true;
}

void
MyListCtrl::StartEditText(int row, int column)
{
	wxRect rect;
	int x0, x1, dx, size, xpos, ypos, xunit, yunit;
	if (editText != NULL && editText->IsShown())
		EndEditText(true);
	if (dataSource == NULL || !dataSource->IsItemEditable(this, row, column))
		return;

	/*  Select only this row  */
	x1 = GetItemCount();
	for (x0 = 0; x0 < x1; x0++)
		SetItemState(x0, (x0 == row ? wxLIST_STATE_SELECTED : 0), wxLIST_STATE_SELECTED);
	
	//  Call the event handler directly
	//  (Otherwise, the table selection may be updated from the "current" selection in the molecule
	//  before the selection is updated from the table)
	wxCommandEvent dummyEvent;
	OnTableSelectionChanged(dummyEvent);

	/*  Scroll the list so that the editing item is visible  */
	EnsureVisible(row);
	GetItemRectForRowAndColumn(rect, row, column);
	rect.Inflate(1, 2);
	editRow = row;
	editColumn = column;
	GetScrollPixelsPerUnit(&xunit, &yunit);
	xpos = GetScrollPos(wxHORIZONTAL);
	ypos = GetScrollPos(wxVERTICAL);
	x0 = rect.GetX();
	x1 = x0 + rect.GetWidth();
	if (x0 < 0) {
		/*  Scroll right  */
		dx = x0 / xunit - 1;
		if (xpos + dx < 0)
			dx = -xpos;
	} else if (x1 > (size = GetSize().GetWidth())) {
		/*  Scroll left  */
		dx = ((x1 - size) / xunit) + 1;
	} else dx = 0;
	if (dx != 0) {
		Scroll(xpos + dx, -1);
	//	Refresh();
		rect.x += dx * xunit;
	}

	/*  Reposition the rect relative to the origin of the scrolling area  */
	GetItemRectForRowAndColumn(rect, row, column);
	rect.Inflate(1, 2);
	ClientToScreen(&rect.x, &rect.y);
	((wxWindow *)m_mainWin)->ScreenToClient(&rect.x, &rect.y);
	
#if defined(__WXMSW__)
	//  wxMSW seems to require that rect.y >= 0
//	if (rect.y < 0)
//		rect.y = 0;
#endif
	
	wxString str = dataSource->GetItemText(this, editRow, editColumn);
	if (editText == NULL) {
		/*  m_mainWin is a protected member in wxGenericListCtrl  */
		editText = new wxTextCtrl((wxWindow *)m_mainWin, -1, wxT(""), rect.GetPosition(), rect.GetSize(), wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
		editText->Connect(wxID_ANY, wxEVT_KEY_DOWN, wxKeyEventHandler(MyListCtrl::OnKeyDownOnEditText), NULL, this);
		editText->Connect(wxID_ANY, wxEVT_KILL_FOCUS, wxFocusEventHandler(MyListCtrl::OnKillFocusOnEditText), NULL, this);
	} else {
		editText->SetSize(rect.x, rect.y, rect.width, rect.height, wxSIZE_ALLOW_MINUS_ONE);
		editText->Clear();
		editText->Show();
	}
	editText->AppendText(str);
	editText->SetFocus();
//	editText->Connect(wxID_ANY, wxEVT_IDLE, wxIdleEventHandler(MyListCtrl::OnIdle), NULL, this);

	editText->SetSelection(-1, -1);  //  Select all text
}

void
MyListCtrl::EndEditTextAndRestart(bool setValueFlag, int newRow, int newColumn)
{
	wxString sval;
	if (editText != NULL && editText->IsShown()) {
		if (setValueFlag && dataSource) {
			sval = editText->GetValue();
		}
	//	if (wxWindow::FindFocus() == editText) {
	//		SetFocus();
	//	}
#if defined(__WXMAC__)
		{
			/*  Erase the focus ring  */
			wxRect rect = editText->GetRect();
			rect = rect.Inflate(5, 5);
			//	Refresh(true, &rect);  /*  This somehow leaves lower side of the focus ring to remain  */
			Refresh();
		}
#endif
		//  Temporarily hide until new editing starts
		//  (editText is set to NULL to avoid recursive calling of EndEditText())
		wxTextCtrl *saveEditText = editText;
		editText = NULL;
		saveEditText->Hide();
		editText = saveEditText;
		
		if (setValueFlag && dataSource)
			dataSource->SetItemText(this, editRow, editColumn, sval);

	}
	
	if (newRow >= 0 && newColumn >= 0) {
		StartEditText(newRow, newColumn);
	} else {
		editRow = editColumn = -1;
#if 0 && defined(__WXMAC__)
		if (editText != NULL) {
			editText->Disconnect(wxID_ANY);
			editText->Destroy();
			editText = NULL;
		}
#else
		if (editText != NULL) {
			editText->Move(-1000, -1000);
			editText->Hide();
		}
#endif
	}

}

void
MyListCtrl::EndEditText(bool setValueFlag)
{
	EndEditTextAndRestart(setValueFlag, -1, -1);
}

void
MyListCtrl::OnKillFocusOnEditText(wxFocusEvent &event)
{
	if (editText != NULL && editText->IsShown()) {
		EndEditText(true);
	}
}

void
MyListCtrl::OnIdle(wxIdleEvent &event)
{
	/*
	wxWindow *wp;
	if (editText != NULL && (wp = wxWindow::FindFocus()) != editText) {
		EndEditText(true);
	}
	 */
}

void
MyListCtrl::OnKeyDownOnEditText(wxKeyEvent &event)
{
	int keyCode, ncols, nrows, ecol, erow;
	bool shiftDown;
	if (editText == NULL || !editText->IsShown()) {
		event.Skip();
		return;
	}
	keyCode = event.GetKeyCode();
	ncols = GetColumnCount();
	nrows = GetItemCount();
	shiftDown = (event.GetModifiers() == wxMOD_SHIFT);
	switch (keyCode) {
		case WXK_TAB:
			ecol = editColumn;
			erow = editRow;
			while (1) {
				if (shiftDown) {
					if (ecol == 0) {
						if (erow == 0)
							return;
						ecol = ncols - 1;
						erow--;
					} else {
						ecol--;
					}
				} else {
					if (ecol == ncols - 1) {
						if (erow >= nrows - 1)
							return;
						ecol = 0;
						erow++;
					} else {
						ecol++;
					}
				}
				if (dataSource == NULL || dataSource->IsItemEditable(this, erow, ecol))
					break;
			}
			EndEditTextAndRestart(true, erow, ecol);
			break;
		case WXK_RETURN:
			if (event.GetModifiers() == wxMOD_ALT) {
				printf("alt-return pressed\n"); fflush(stdout);
				EndEditText(true);
				printf("EndEditText completed\n"); fflush(stdout);
				return;
			}
			ecol = editColumn;
			erow = editRow;
			while (1) {
				if (shiftDown) {
					if (erow == 0)
						return;
					erow--;
				} else {
					if (erow == nrows - 1)
						return;
					erow++;
				}
				if (dataSource == NULL || dataSource->IsItemEditable(this, erow, ecol))
					break;
			}
			EndEditTextAndRestart(true, erow, ecol);
			break;
		case WXK_ESCAPE:
			EndEditText(false);
			break;
		default:
			event.Skip();
			break;
	}
}

bool
MyListCtrl::DeleteColumn(int col)
{
	EndEditText(false);
	return wxGenericListCtrl::DeleteColumn(col);
}

bool
MyListCtrl::InsertColumn(long col, const wxString &heading, int format, int width)
{
	EndEditText(false);
	return wxGenericListCtrl::InsertColumn(col, heading, format, width);
}

void
MyListCtrl::OnPopUpMenuSelected(wxCommandEvent &event)
{
	if (dataSource != NULL)
		dataSource->OnPopUpMenuSelected(this, lastPopUpRow, lastPopUpColumn, event.GetId() - 1);
}

void
MyListCtrl::OnLeftDClick(wxMouseEvent &event)
{
	int row, col;
	wxPoint pos = event.GetPosition();
	if (!FindItemAtPosition(pos, &row, &col))
		return;
	if (editText != NULL) {
		if (editRow == row && editColumn == col) {
			event.Skip();
			return;
		}
		EndEditTextAndRestart(true, row, col);
	} else {
		StartEditText(row, col);
	}
}

void
MyListCtrl::OnMouseDown(wxMouseEvent &event)
{
	int row, col, i, n;
	char **items;

	if (editText != NULL && editText->IsShown()) {
		//  During the text edit, mouse down outside the textctrl will terminate the editing
		EndEditText();
	}

	wxPoint pos = event.GetPosition();
	if (FindItemAtPosition(pos, &row, &col) && dataSource != NULL && (n = dataSource->HasPopUpMenu(this, row, col, &items)) > 0) {
		wxMenu mnu;
		for (i = 0; i < n; i++) {
			char *p = items[i];
			bool enabled = true;
			if (*p == '-') {
				if (p[1] == 0) {
					//  Separator
					mnu.AppendSeparator();
					p = NULL;
				} else {
					//  Disabled item
					p++;
					enabled = false;
				}
			}
			if (p != NULL) {
				wxString itemStr(p, WX_DEFAULT_CONV);
				mnu.Append(i + 1, itemStr);
				if (!enabled)
					mnu.Enable(i + 1, false);
			}
			free(items[i]);
			items[i] = NULL;
		}
		free(items);
		lastPopUpColumn = col;
		lastPopUpRow = row;
		mnu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MyListCtrl::OnPopUpMenuSelected), NULL, this);
		PopupMenu(&mnu);
		n = dataSource->GetItemCount(this);
		for (i = 0; i < n; i++)
			SetItemState(i, (i == row ? wxLIST_STATE_SELECTED : 0), wxLIST_STATE_SELECTED);
		PostSelectionChangeNotification();
		return;
	}
	
	//  Intercept mouse down event and post selection change notification
	//  (a workaround of wxMSW problem where EVT_LIST_ITEM_SELECTED is not sent in some occasions)
	PostSelectionChangeNotification();
	event.Skip();
}

void
MyListCtrl::OnChar(wxKeyEvent &event)
{
	//  See comments on OnMouseUp()
	PostSelectionChangeNotification();
	event.Skip();
}

void
MyListCtrl::OnItemSelectionChanged(wxListEvent &event)
{
	PostSelectionChangeNotification();
}

void
MyListCtrl::OnTableSelectionChanged(wxCommandEvent &event)
{
	selectionChangeNotificationSent = false;
	if (dataSource == NULL)
		return;
	dataSource->OnSelectionChanged(this);
}

void
MyListCtrl::OnEnableTableSelectionNotification(wxCommandEvent &event)
{
	selectionChangeNotificationEnabled = true;
}
Show on old repository browser