OSDN Git Service

The text edit in MyListCtrl seems weird. Hopefully fixed.
[molby/Molby.git] / wxSources / MyListCtrl.cpp
1 /*
2  *  MyListCtrl.cpp
3  *  Molby
4  *
5  *  Created by Toshi Nagata on 08/12/09.
6  *  Copyright 2008 Toshi Nagata. All rights reserved.
7  *
8  This program is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation version 2 of the License.
11  
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  GNU General Public License for more details.
16  */
17
18 #include "MyListCtrl.h"
19 #include "MyMBConv.h"
20
21 #include "wx/dcclient.h"
22 #include "wx/scrolwin.h"
23 #include "wx/glcanvas.h"
24 #include "wx/menu.h"
25
26 const wxEventType MyListCtrlEvent = wxNewEventType();
27
28 IMPLEMENT_DYNAMIC_CLASS(MyListCtrl, wxGenericListCtrl)
29
30 BEGIN_EVENT_TABLE(MyListCtrl, wxGenericListCtrl)
31 EVT_LIST_ITEM_SELECTED(-1, MyListCtrl::OnItemSelectionChanged)
32 EVT_LIST_ITEM_DESELECTED(-1, MyListCtrl::OnItemSelectionChanged)
33 EVT_COMMAND(MyListCtrlEvent_tableSelectionChanged, MyListCtrlEvent, MyListCtrl::OnTableSelectionChanged)
34 EVT_COMMAND(MyListCtrlEvent_enableTableSelectionNotification, MyListCtrlEvent, MyListCtrl::OnEnableTableSelectionNotification)
35 EVT_LIST_BEGIN_DRAG(-1, MyListCtrl::OnBeginDrag)
36 EVT_LEFT_DCLICK(MyListCtrl::OnLeftDClick)
37 EVT_CHAR(MyListCtrl::OnChar)
38 EVT_LEFT_DOWN(MyListCtrl::OnMouseDown)
39 END_EVENT_TABLE()
40
41 MyListCtrl::MyListCtrl()
42 {
43         editText = NULL;
44 #if defined(__WXMAC__)
45         //  On OSX, the default font seems to be 14-point, which is too big.
46         wxFont font = this->GetFont();
47         font.SetPointSize(12);
48         this->SetFont(font);
49 #endif
50 }
51
52 MyListCtrl::~MyListCtrl()
53 {
54         if (editText != NULL) {
55         /*      editText->Destroy(); */  /*  May be unnecessary  */
56                 editText = NULL;
57         }
58 }
59
60 bool
61 MyListCtrl::Create(wxWindow* parent, wxWindowID wid, const wxPoint& pos, const wxSize& size)
62 {
63         this->wxGenericListCtrl::Create(parent, wid, pos, size, wxLC_REPORT | wxLC_VIRTUAL | wxBORDER_SIMPLE);
64         dataSource = NULL;
65         editText = NULL;
66         selectionChangeNotificationSent = false;
67         selectionChangeNotificationEnabled = true;
68         subTitleRowAttr = new wxListItemAttr;
69         dragTargetRow = -1;
70         return true;
71 }
72
73 void
74 MyListCtrl::SetDataSource(MyListCtrlDataSource *source)
75 {
76         dataSource = source;
77         RefreshTable();
78 }
79
80 void
81 MyListCtrl::RefreshTable()
82 {
83         if (dataSource != NULL) {
84                 int nrows = dataSource->GetItemCount(this);
85                 SetItemCount(nrows);
86                 if (nrows > 0) {
87                         RefreshItems(0, nrows - 1);
88                 }
89         }
90 }
91
92 // Define the repainting behaviour
93 void
94 MyListCtrl::OnPaintCallback(wxDC *dc)
95 {
96         if (dragTargetRow >= 0) {
97                 wxRect r;
98                 wxPen pen = *wxCYAN_PEN;
99                 int dx, dy, y;
100 #if defined(__WXMSW__)
101                 static const int offset_y = -6;
102 #else
103                 static const int offset_y = 0;
104 #endif
105                 CalcScrolledPosition(0, 0, &dx, &dy);   
106                 pen.SetWidth(3);
107                 dc->SetPen(pen);
108                 if (dragTargetRow == GetItemCount()) {
109                         GetItemRect(dragTargetRow - 1, r);
110                         y = r.y - dy;
111                 } else {
112                         GetItemRect(dragTargetRow, r);
113                         y = r.y - dy - r.height;
114                 }
115                 y += offset_y;
116         //      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);
117                 dc->DrawLine(r.x - dx, y, r.x - dx + r.width, y);
118         }
119 }
120
121 //  Callback function that is called from wxListMainWindow::OnPaint().
122 //  This is a very ugly hack, but I can think of no alternative...
123 void
124 wxListCtrl_onPaintCallback(wxGenericListCtrl *listctrl, wxDC *dc)
125 {
126         MyListCtrl *ctrl = wxStaticCast(listctrl, MyListCtrl);
127         if (ctrl != NULL && dc != NULL)
128                 ctrl->OnPaintCallback(dc);
129 }
130
131 wxString
132 MyListCtrl::OnGetItemText(long item, long column) const
133 {
134         if (dataSource == NULL)
135                 return wxEmptyString;
136         return dataSource->GetItemText((MyListCtrl *)this, item, column);
137 }
138
139 wxListItemAttr *
140 MyListCtrl::OnGetItemAttr(long item) const
141 {
142         float fg[3], bg[3];
143         long row, col;
144         int ret;
145         row = item % 1000000;
146         col = item / 1000000 - 1;  //  -1 for all rows
147         ret = dataSource->SetItemColor((MyListCtrl *)this, row, col, fg, bg);
148         if (ret == 0)
149                 return NULL;
150         if (ret & 1) {
151                 wxColour fgcol((int)(fg[0] * 255), (int)(fg[1] * 255), (int)(fg[2] * 255));
152                 subTitleRowAttr->SetTextColour(fgcol);
153         } else subTitleRowAttr->SetTextColour(*wxBLACK);
154         if (ret & 2) {
155                 wxColour bgcol((int)(bg[0] * 255), (int)(bg[1] * 255), (int)(bg[2] * 255));
156                 subTitleRowAttr->SetBackgroundColour(bgcol);
157         } else subTitleRowAttr->SetBackgroundColour(*wxWHITE);
158         return subTitleRowAttr;
159 }
160
161 void
162 MyListCtrl::PostSelectionChangeNotification()
163 {
164         if (!selectionChangeNotificationSent && selectionChangeNotificationEnabled) {
165                 selectionChangeNotificationSent = true;
166                 wxCommandEvent myEvent(MyListCtrlEvent, MyListCtrlEvent_tableSelectionChanged);
167                 wxPostEvent(this, myEvent);
168         }
169 }
170
171 void
172 MyListCtrl::OnBeginLabelEdit(wxListEvent &event)
173 {
174 //      printf("OnBeginLabelEdit: item index = %d\n", event.GetIndex());
175 }
176
177 void
178 MyListCtrl::OnEndLabelEdit(wxListEvent &event)
179 {
180 //      printf("OnEndLabelEdit: item index = %d\n", event.GetIndex());
181 }
182
183 void
184 MyListCtrl::OnItemActivated(wxListEvent &event)
185 {
186 //      printf("OnItemActivated: item index = %d, col = %d\n", event.GetIndex(), event.GetItem().m_col);
187 }
188
189 void
190 MyListCtrl::OnBeginDrag(wxListEvent &event)
191 {
192         int count = GetItemCount();
193         
194         if (dataSource == NULL || !dataSource->IsDragAndDropEnabled(this))
195                 return;
196         EndEditText(true);
197         dragTargetRow = -1;
198         while (1) {
199                 wxRect r;
200                 wxMouseState mstate = wxGetMouseState();
201                 wxPoint pt(mstate.GetX(), mstate.GetY());
202                 pt = ScreenToClient(pt);
203                 long newRow = FindItem(-1, pt, 0);
204                 if (newRow != dragTargetRow) {
205                         if (newRow >= 0)
206                                 EnsureVisible(newRow);
207                         else {
208                                 GetItemRect(0, r);
209                                 if (pt.y < r.y)
210                                         EnsureVisible(0);
211                                 else {
212                                         GetItemRect(count - 1, r);
213                                         if (pt.y >= r.y) {
214                                                 EnsureVisible(count - 1);
215                                                 if (pt.y < r.y + r.height)
216                                                         newRow = count;
217                                         } else if (count > 0 && pt.y >= r.y - r.height)
218                                                 newRow = count - 1;
219                                 }
220                         }
221                 }
222                 if (newRow != dragTargetRow) {
223                         if (newRow >= 0) {
224                                 if (newRow == count) {
225                                         GetItemRect(newRow - 1, r);
226                                         r.y += r.height / 2;
227                                 } else {
228                                         GetItemRect(newRow, r);
229                                         r.y -= r.height / 2;
230                                 }
231                                 RefreshRect(r);
232                         }
233                         if (dragTargetRow >= 0) {
234                                 if (dragTargetRow == count) {
235                                         GetItemRect(dragTargetRow - 1, r);
236                                         r.y += r.height / 2;
237                                 } else {
238                                         GetItemRect(dragTargetRow, r);
239                                         r.y -= r.height / 2;
240                                 }
241                                 RefreshRect(r);
242                         }
243                         dragTargetRow = newRow;
244                         Update();
245                 }
246                 if (!mstate.LeftIsDown()) {
247                         //  If the mouse cursor is outside the item rect, then dragging should be discarded
248                         if (dragTargetRow >= 0) {
249                                 r = GetClientRect();
250                                 if (!r.Contains(pt))
251                                         dragTargetRow = -1;
252                         }
253                         break;
254                 }
255         }
256         if (dragTargetRow >= 0)
257                 dataSource->DragSelectionToRow(this, dragTargetRow);
258         dragTargetRow = -1;
259         Update();
260 }
261
262 bool
263 MyListCtrl::GetItemRectForRowAndColumn(wxRect &rect, int row, int column)
264 {
265         int i, xpos, width, xunit, yunit;
266         if (!GetItemRect(row, rect))
267                 return false;
268         GetScrollPixelsPerUnit(&xunit, &yunit);
269         xpos = -GetScrollPos(wxHORIZONTAL) * xunit;
270         for (i = 0; i < column; i++) {
271                 width = GetColumnWidth(i);
272                 xpos += width;
273         }
274         rect.SetX(xpos);
275         rect.SetWidth(GetColumnWidth(column));
276         return true;
277 }
278
279 void
280 MyListCtrl::GetScrollPixelsPerUnit(int *xunit, int *yunit)
281 {
282         wxGenericListCtrl::GetScrollPixelsPerUnit(xunit, yunit);
283 //      int x, y;
284 //      /*  m_mainWin is a protected member in wxGenericListCtrl  */
285 //      /*((wxScrolledWindow *)m_mainWin)->*/GetScrollPixelsPerUnit(&x, &y);    
286 //      if (xunit != NULL)
287 //              *xunit = x;
288 //      if (yunit != NULL)
289 //              *yunit = y;
290 }
291
292 bool
293 MyListCtrl::FindItemAtPosition(const wxPoint &pos, int *row, int *column)
294 {
295         int i, r, ncols, width, xpos, flags, xunit, yunit;
296         r = (int)HitTest(pos, flags, NULL);
297         if (r == wxNOT_FOUND)
298                 return false;
299         ncols = GetColumnCount();
300         GetScrollPixelsPerUnit(&xunit, &yunit);
301         xpos = -GetScrollPos(wxHORIZONTAL) * xunit;
302         for (i = 0; i < ncols; i++) {
303                 width = GetColumnWidth(i);
304                 xpos += width;
305                 if (pos.x < xpos)
306                         break;
307         }
308         if (i >= ncols)
309                 return false;
310         *row = r;
311         *column = i;
312         return true;
313 }
314
315 void
316 MyListCtrl::StartEditText(int row, int column)
317 {
318         wxRect rect;
319         int x0, x1, y0, dx, size, xpos, ypos, xunit, yunit;
320         if (editText != NULL && editText->IsShown())
321                 EndEditText(true);
322         if (dataSource == NULL || !dataSource->IsItemEditable(this, row, column))
323                 return;
324
325         /*  Select only this row  */
326         x1 = GetItemCount();
327         for (x0 = 0; x0 < x1; x0++)
328                 SetItemState(x0, (x0 == row ? wxLIST_STATE_SELECTED : 0), wxLIST_STATE_SELECTED);
329         
330         //  Call the event handler directly
331         //  (Otherwise, the table selection may be updated from the "current" selection in the molecule
332         //  before the selection is updated from the table)
333         wxCommandEvent dummyEvent;
334         OnTableSelectionChanged(dummyEvent);
335
336         /*  Scroll the list so that the editing item is visible  */
337         EnsureVisible(row);
338         GetItemRectForRowAndColumn(rect, row, column);
339         rect.Inflate(1, 2);
340         editRow = row;
341         editColumn = column;
342         GetScrollPixelsPerUnit(&xunit, &yunit);
343         xpos = GetScrollPos(wxHORIZONTAL);
344         ypos = GetScrollPos(wxVERTICAL);
345         x0 = rect.GetX();
346         x1 = x0 + rect.GetWidth();
347         if (x0 < 0) {
348                 /*  Scroll right  */
349                 dx = x0 / xunit - 1;
350                 if (xpos + dx < 0)
351                         dx = -xpos;
352         } else if (x1 > (size = GetSize().GetWidth())) {
353                 /*  Scroll left  */
354                 dx = ((x1 - size) / xunit) + 1;
355         } else dx = 0;
356         if (dx != 0) {
357                 Scroll(xpos + dx, -1);
358         //      Refresh();
359                 rect.x += dx * xunit;
360         }
361
362         /*  Reposition the rect relative to the origin of the scrolling area  */
363         GetItemRectForRowAndColumn(rect, row, column);
364         rect.Inflate(1, 2);
365         ClientToScreen(&rect.x, &rect.y);
366         ((wxWindow *)m_mainWin)->ScreenToClient(&rect.x, &rect.y);
367         
368 #if defined(__WXMSW__)
369         //  wxMSW seems to require that rect.y >= 0
370 //      if (rect.y < 0)
371 //              rect.y = 0;
372 #endif
373         
374         wxString str = dataSource->GetItemText(this, editRow, editColumn);
375         if (editText == NULL) {
376                 /*  m_mainWin is a protected member in wxGenericListCtrl  */
377                 editText = new wxTextCtrl((wxWindow *)m_mainWin, -1, wxT(""), rect.GetPosition(), rect.GetSize(), wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
378                 editText->Connect(wxID_ANY, wxEVT_KEY_DOWN, wxKeyEventHandler(MyListCtrl::OnKeyDownOnEditText), NULL, this);
379                 editText->Connect(wxID_ANY, wxEVT_KILL_FOCUS, wxFocusEventHandler(MyListCtrl::OnKillFocusOnEditText), NULL, this);
380         } else {
381                 editText->SetSize(rect.x, rect.y, rect.width, rect.height, wxSIZE_ALLOW_MINUS_ONE);
382                 editText->Clear();
383                 editText->Show();
384         }
385         editText->AppendText(str);
386         editText->SetFocus();
387 //      editText->Connect(wxID_ANY, wxEVT_IDLE, wxIdleEventHandler(MyListCtrl::OnIdle), NULL, this);
388
389         editText->SetSelection(-1, -1);  //  Select all text
390 }
391
392 void
393 MyListCtrl::EndEditTextAndRestart(bool setValueFlag, int newRow, int newColumn)
394 {
395         wxString sval;
396         if (editText != NULL && editText->IsShown()) {
397                 if (setValueFlag && dataSource) {
398                         sval = editText->GetValue();
399                 }
400                 if (wxWindow::FindFocus() == editText) {
401                         SetFocus();
402                 }
403 #if defined(__WXMAC__)
404                 {
405                         /*  Erase the focus ring  */
406                         wxRect rect = editText->GetRect();
407                         rect = rect.Inflate(5, 5);
408                         //      Refresh(true, &rect);  /*  This somehow leaves lower side of the focus ring to remain  */
409                         Refresh();
410                 }
411 #endif
412                 editText->Hide();  /*  Temporarily hide until new editing starts  */
413
414                 if (setValueFlag && dataSource)
415                         dataSource->SetItemText(this, editRow, editColumn, sval);
416
417         }
418         
419         if (newRow >= 0 && newColumn >= 0) {
420                 StartEditText(newRow, newColumn);
421         } else {
422                 editRow = editColumn = -1;
423 #if defined(__WXMAC__)
424                 if (editText != NULL) {
425                         editText->Disconnect(wxID_ANY);
426                         editText->Destroy();
427                         editText = NULL;
428                 }
429 #else
430                 if (editText != NULL) {
431                         editText->Move(-1000, -1000);
432                         editText->Hide();
433                 }
434 #endif
435         }
436
437 }
438
439 void
440 MyListCtrl::EndEditText(bool setValueFlag)
441 {
442         EndEditTextAndRestart(setValueFlag, -1, -1);
443 }
444
445 void
446 MyListCtrl::OnKillFocusOnEditText(wxFocusEvent &event)
447 {
448         if (editText != NULL && editText->IsShown()) {
449                 EndEditText(true);
450         }
451 }
452
453 void
454 MyListCtrl::OnIdle(wxIdleEvent &event)
455 {
456         /*
457         wxWindow *wp;
458         if (editText != NULL && (wp = wxWindow::FindFocus()) != editText) {
459                 EndEditText(true);
460         }
461          */
462 }
463
464 void
465 MyListCtrl::OnKeyDownOnEditText(wxKeyEvent &event)
466 {
467         int keyCode, ncols, nrows, ecol, erow;
468         bool shiftDown;
469         if (editText == NULL || !editText->IsShown()) {
470                 event.Skip();
471                 return;
472         }
473         keyCode = event.GetKeyCode();
474         ncols = GetColumnCount();
475         nrows = GetItemCount();
476         shiftDown = (event.GetModifiers() == wxMOD_SHIFT);
477         switch (keyCode) {
478                 case WXK_TAB:
479                         ecol = editColumn;
480                         erow = editRow;
481                         while (1) {
482                                 if (shiftDown) {
483                                         if (ecol == 0) {
484                                                 if (erow == 0)
485                                                         return;
486                                                 ecol = ncols - 1;
487                                                 erow--;
488                                         } else {
489                                                 ecol--;
490                                         }
491                                 } else {
492                                         if (ecol == ncols - 1) {
493                                                 if (erow >= nrows - 1)
494                                                         return;
495                                                 ecol = 0;
496                                                 erow++;
497                                         } else {
498                                                 ecol++;
499                                         }
500                                 }
501                                 if (dataSource == NULL || dataSource->IsItemEditable(this, erow, ecol))
502                                         break;
503                         }
504                         EndEditTextAndRestart(true, erow, ecol);
505                         break;
506                 case WXK_RETURN:
507                         if (event.GetModifiers() == wxMOD_ALT) {
508                                 printf("alt-return pressed\n"); fflush(stdout);
509                                 EndEditText(true);
510                                 printf("EndEditText completed\n"); fflush(stdout);
511                                 return;
512                         }
513                         ecol = editColumn;
514                         erow = editRow;
515                         while (1) {
516                                 if (shiftDown) {
517                                         if (erow == 0)
518                                                 return;
519                                         erow--;
520                                 } else {
521                                         if (erow == nrows - 1)
522                                                 return;
523                                         erow++;
524                                 }
525                                 if (dataSource == NULL || dataSource->IsItemEditable(this, erow, ecol))
526                                         break;
527                         }
528                         EndEditTextAndRestart(true, erow, ecol);
529                         break;
530                 case WXK_ESCAPE:
531                         EndEditText(false);
532                         break;
533                 default:
534                         event.Skip();
535                         break;
536         }
537 }
538
539 bool
540 MyListCtrl::DeleteColumn(int col)
541 {
542         EndEditText(false);
543         return wxGenericListCtrl::DeleteColumn(col);
544 }
545
546 bool
547 MyListCtrl::InsertColumn(long col, const wxString &heading, int format, int width)
548 {
549         EndEditText(false);
550         return wxGenericListCtrl::InsertColumn(col, heading, format, width);
551 }
552
553 void
554 MyListCtrl::OnPopUpMenuSelected(wxCommandEvent &event)
555 {
556         if (dataSource != NULL)
557                 dataSource->OnPopUpMenuSelected(this, lastPopUpRow, lastPopUpColumn, event.GetId() - 1);
558 }
559
560 void
561 MyListCtrl::OnLeftDClick(wxMouseEvent &event)
562 {
563         int row, col;
564         wxPoint pos = event.GetPosition();
565         if (!FindItemAtPosition(pos, &row, &col))
566                 return;
567         if (editText != NULL) {
568                 if (editRow == row && editColumn == col) {
569                         event.Skip();
570                         return;
571                 }
572                 EndEditTextAndRestart(true, row, col);
573         } else {
574                 StartEditText(row, col);
575         }
576 }
577
578 void
579 MyListCtrl::OnMouseDown(wxMouseEvent &event)
580 {
581         int row, col, i, n;
582         char **items;
583
584         if (editText != NULL && editText->IsShown()) {
585                 //  During the text edit, mouse down outside the textctrl will terminate the editing
586                 EndEditText();
587         }
588
589         wxPoint pos = event.GetPosition();
590         if (FindItemAtPosition(pos, &row, &col) && dataSource != NULL && (n = dataSource->HasPopUpMenu(this, row, col, &items)) > 0) {
591                 wxMenu mnu;
592                 for (i = 0; i < n; i++) {
593                         wxString itemStr(items[i], WX_DEFAULT_CONV);
594                         mnu.Append(i + 1, itemStr);
595                         free(items[i]);
596                         items[i] = NULL;
597                 }
598                 free(items);
599                 lastPopUpColumn = col;
600                 lastPopUpRow = row;
601                 mnu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MyListCtrl::OnPopUpMenuSelected), NULL, this);
602                 PopupMenu(&mnu);
603                 n = dataSource->GetItemCount(this);
604                 for (i = 0; i < n; i++)
605                         SetItemState(i, (i == row ? wxLIST_STATE_SELECTED : 0), wxLIST_STATE_SELECTED);
606                 PostSelectionChangeNotification();
607                 return;
608         }
609         
610         //  Intercept mouse down event and post selection change notification
611         //  (a workaround of wxMSW problem where EVT_LIST_ITEM_SELECTED is not sent in some occasions)
612         PostSelectionChangeNotification();
613         event.Skip();
614 }
615
616 void
617 MyListCtrl::OnChar(wxKeyEvent &event)
618 {
619         //  See comments on OnMouseUp()
620         PostSelectionChangeNotification();
621         event.Skip();
622 }
623
624 void
625 MyListCtrl::OnItemSelectionChanged(wxListEvent &event)
626 {
627         PostSelectionChangeNotification();
628 }
629
630 void
631 MyListCtrl::OnTableSelectionChanged(wxCommandEvent &event)
632 {
633         selectionChangeNotificationSent = false;
634         if (dataSource == NULL)
635                 return;
636         dataSource->OnSelectionChanged(this);
637 }
638
639 void
640 MyListCtrl::OnEnableTableSelectionNotification(wxCommandEvent &event)
641 {
642         selectionChangeNotificationEnabled = true;
643 }