OSDN Git Service

A couple of bugfix in the Document. The version number is set to 1.0b1.
[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, 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                 //  Temporarily hide until new editing starts
413                 //  (editText is set to NULL to avoid recursive calling of EndEditText())
414                 wxTextCtrl *saveEditText = editText;
415                 editText = NULL;
416                 saveEditText->Hide();
417                 editText = saveEditText;
418                 
419                 if (setValueFlag && dataSource)
420                         dataSource->SetItemText(this, editRow, editColumn, sval);
421
422         }
423         
424         if (newRow >= 0 && newColumn >= 0) {
425                 StartEditText(newRow, newColumn);
426         } else {
427                 editRow = editColumn = -1;
428 #if 0 && defined(__WXMAC__)
429                 if (editText != NULL) {
430                         editText->Disconnect(wxID_ANY);
431                         editText->Destroy();
432                         editText = NULL;
433                 }
434 #else
435                 if (editText != NULL) {
436                         editText->Move(-1000, -1000);
437                         editText->Hide();
438                 }
439 #endif
440         }
441
442 }
443
444 void
445 MyListCtrl::EndEditText(bool setValueFlag)
446 {
447         EndEditTextAndRestart(setValueFlag, -1, -1);
448 }
449
450 void
451 MyListCtrl::OnKillFocusOnEditText(wxFocusEvent &event)
452 {
453         if (editText != NULL && editText->IsShown()) {
454                 EndEditText(true);
455         }
456 }
457
458 void
459 MyListCtrl::OnIdle(wxIdleEvent &event)
460 {
461         /*
462         wxWindow *wp;
463         if (editText != NULL && (wp = wxWindow::FindFocus()) != editText) {
464                 EndEditText(true);
465         }
466          */
467 }
468
469 void
470 MyListCtrl::OnKeyDownOnEditText(wxKeyEvent &event)
471 {
472         int keyCode, ncols, nrows, ecol, erow;
473         bool shiftDown;
474         if (editText == NULL || !editText->IsShown()) {
475                 event.Skip();
476                 return;
477         }
478         keyCode = event.GetKeyCode();
479         ncols = GetColumnCount();
480         nrows = GetItemCount();
481         shiftDown = (event.GetModifiers() == wxMOD_SHIFT);
482         switch (keyCode) {
483                 case WXK_TAB:
484                         ecol = editColumn;
485                         erow = editRow;
486                         while (1) {
487                                 if (shiftDown) {
488                                         if (ecol == 0) {
489                                                 if (erow == 0)
490                                                         return;
491                                                 ecol = ncols - 1;
492                                                 erow--;
493                                         } else {
494                                                 ecol--;
495                                         }
496                                 } else {
497                                         if (ecol == ncols - 1) {
498                                                 if (erow >= nrows - 1)
499                                                         return;
500                                                 ecol = 0;
501                                                 erow++;
502                                         } else {
503                                                 ecol++;
504                                         }
505                                 }
506                                 if (dataSource == NULL || dataSource->IsItemEditable(this, erow, ecol))
507                                         break;
508                         }
509                         EndEditTextAndRestart(true, erow, ecol);
510                         break;
511                 case WXK_RETURN:
512                         if (event.GetModifiers() == wxMOD_ALT) {
513                                 printf("alt-return pressed\n"); fflush(stdout);
514                                 EndEditText(true);
515                                 printf("EndEditText completed\n"); fflush(stdout);
516                                 return;
517                         }
518                         ecol = editColumn;
519                         erow = editRow;
520                         while (1) {
521                                 if (shiftDown) {
522                                         if (erow == 0)
523                                                 return;
524                                         erow--;
525                                 } else {
526                                         if (erow == nrows - 1)
527                                                 return;
528                                         erow++;
529                                 }
530                                 if (dataSource == NULL || dataSource->IsItemEditable(this, erow, ecol))
531                                         break;
532                         }
533                         EndEditTextAndRestart(true, erow, ecol);
534                         break;
535                 case WXK_ESCAPE:
536                         EndEditText(false);
537                         break;
538                 default:
539                         event.Skip();
540                         break;
541         }
542 }
543
544 bool
545 MyListCtrl::DeleteColumn(int col)
546 {
547         EndEditText(false);
548         return wxGenericListCtrl::DeleteColumn(col);
549 }
550
551 bool
552 MyListCtrl::InsertColumn(long col, const wxString &heading, int format, int width)
553 {
554         EndEditText(false);
555         return wxGenericListCtrl::InsertColumn(col, heading, format, width);
556 }
557
558 void
559 MyListCtrl::OnPopUpMenuSelected(wxCommandEvent &event)
560 {
561         if (dataSource != NULL)
562                 dataSource->OnPopUpMenuSelected(this, lastPopUpRow, lastPopUpColumn, event.GetId() - 1);
563 }
564
565 void
566 MyListCtrl::OnLeftDClick(wxMouseEvent &event)
567 {
568         int row, col;
569         wxPoint pos = event.GetPosition();
570         if (!FindItemAtPosition(pos, &row, &col))
571                 return;
572         if (editText != NULL) {
573                 if (editRow == row && editColumn == col) {
574                         event.Skip();
575                         return;
576                 }
577                 EndEditTextAndRestart(true, row, col);
578         } else {
579                 StartEditText(row, col);
580         }
581 }
582
583 void
584 MyListCtrl::OnMouseDown(wxMouseEvent &event)
585 {
586         int row, col, i, n;
587         char **items;
588
589         if (editText != NULL && editText->IsShown()) {
590                 //  During the text edit, mouse down outside the textctrl will terminate the editing
591                 EndEditText();
592         }
593
594         wxPoint pos = event.GetPosition();
595         if (FindItemAtPosition(pos, &row, &col) && dataSource != NULL && (n = dataSource->HasPopUpMenu(this, row, col, &items)) > 0) {
596                 wxMenu mnu;
597                 for (i = 0; i < n; i++) {
598                         char *p = items[i];
599                         bool enabled = true;
600                         if (*p == '-') {
601                                 if (p[1] == 0) {
602                                         //  Separator
603                                         mnu.AppendSeparator();
604                                         p = NULL;
605                                 } else {
606                                         //  Disabled item
607                                         p++;
608                                         enabled = false;
609                                 }
610                         }
611                         if (p != NULL) {
612                                 wxString itemStr(p, WX_DEFAULT_CONV);
613                                 mnu.Append(i + 1, itemStr);
614                                 if (!enabled)
615                                         mnu.Enable(i + 1, false);
616                         }
617                         free(items[i]);
618                         items[i] = NULL;
619                 }
620                 free(items);
621                 lastPopUpColumn = col;
622                 lastPopUpRow = row;
623                 mnu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MyListCtrl::OnPopUpMenuSelected), NULL, this);
624                 PopupMenu(&mnu);
625                 n = dataSource->GetItemCount(this);
626                 for (i = 0; i < n; i++)
627                         SetItemState(i, (i == row ? wxLIST_STATE_SELECTED : 0), wxLIST_STATE_SELECTED);
628                 PostSelectionChangeNotification();
629                 return;
630         }
631         
632         //  Intercept mouse down event and post selection change notification
633         //  (a workaround of wxMSW problem where EVT_LIST_ITEM_SELECTED is not sent in some occasions)
634         PostSelectionChangeNotification();
635         event.Skip();
636 }
637
638 void
639 MyListCtrl::OnChar(wxKeyEvent &event)
640 {
641         //  See comments on OnMouseUp()
642         PostSelectionChangeNotification();
643         event.Skip();
644 }
645
646 void
647 MyListCtrl::OnItemSelectionChanged(wxListEvent &event)
648 {
649         PostSelectionChangeNotification();
650 }
651
652 void
653 MyListCtrl::OnTableSelectionChanged(wxCommandEvent &event)
654 {
655         selectionChangeNotificationSent = false;
656         if (dataSource == NULL)
657                 return;
658         dataSource->OnSelectionChanged(this);
659 }
660
661 void
662 MyListCtrl::OnEnableTableSelectionNotification(wxCommandEvent &event)
663 {
664         selectionChangeNotificationEnabled = true;
665 }