1 /** DGui project file.
2 
3 Copyright: Trogu Antonio Davide 2011-2013
4 
5 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
6 
7 Authors: Trogu Antonio Davide
8 */
9 module dguihub.listview;
10 
11 import std.utf : toUTFz;
12 public import dguihub.core.controls.ownerdrawcontrol;
13 import dguihub.core.utils;
14 import dguihub.imagelist;
15 
16 enum ColumnTextAlign : int {
17    left = LVCFMT_LEFT,
18    center = LVCFMT_CENTER,
19    right = LVCFMT_RIGHT,
20 }
21 
22 enum ViewStyle : uint {
23    list = LVS_LIST,
24    report = LVS_REPORT,
25    largeIcon = LVS_ICON,
26    smallIcon = LVS_SMALLICON,
27 }
28 
29 enum ListViewBits : ubyte {
30    none = 0,
31    gridLines = 1,
32    fullRowSelect = 2,
33    checkBoxes = 4,
34 }
35 
36 class ListViewItem {
37    private Collection!(ListViewItem) _subItems;
38    private bool _checked = false;
39    private ListViewItem _parentItem;
40    private ListView _owner;
41    private string _text;
42    private int _imgIdx;
43 
44    mixin tagProperty;
45 
46    package this(ListView owner, string txt, int imgIdx, bool check) {
47       this._checked = check;
48       this._imgIdx = imgIdx;
49       this._owner = owner;
50       this._text = txt;
51    }
52 
53    package this(ListView owner, ListViewItem parentItem, string txt, int imgIdx, bool check) {
54       this._parentItem = parentItem;
55       this(owner, txt, imgIdx, check);
56    }
57 
58    @property public final int index() {
59       if (this._owner) {
60          foreach (int i, ListViewItem lvi; this._owner.items) {
61             if (lvi is(this._parentItem ? this._parentItem : this)) {
62                return i;
63             }
64          }
65       }
66 
67       return -1;
68    }
69 
70    @property public final int imageIndex() {
71       return this._imgIdx;
72    }
73 
74    @property public final void imageIndex(int imgIdx) {
75       if (this._parentItem) {
76          return;
77       }
78 
79       this._imgIdx = imgIdx;
80 
81       if (this._owner && this._owner.created) {
82          LVITEMW lvi;
83 
84          lvi.mask = LVIF_IMAGE;
85          lvi.iItem = this.index;
86          lvi.iSubItem = 0;
87          lvi.iImage = imgIdx;
88 
89          this._owner.sendMessage(LVM_SETITEMW, 0, cast(LPARAM)&lvi);
90       }
91    }
92 
93    @property public final string text() {
94       return this._text;
95    }
96 
97    @property public final void text(string s) {
98       this._text = s;
99 
100       if (this._owner && this._owner.created) {
101          LVITEMW lvi;
102 
103          lvi.mask = LVIF_TEXT;
104          lvi.iItem = this.index;
105          lvi.iSubItem = !this._parentItem ? 0 : this.subitemIndex;
106          lvi.pszText = toUTFz!(wchar*)(s);
107 
108          this._owner.sendMessage(LVM_SETITEMW, 0, cast(LPARAM)&lvi);
109       }
110    }
111 
112    @property package bool internalChecked() {
113       return this._checked;
114    }
115 
116    @property public final bool checked() {
117       if (this._owner && this._owner.created) {
118          return cast(bool)((this._owner.sendMessage(LVM_GETITEMSTATE,
119                this.index, LVIS_STATEIMAGEMASK) >> 12) - 1);
120       }
121 
122       return this._checked;
123    }
124 
125    @property public final void checked(bool b) {
126       if (this._parentItem) {
127          return;
128       }
129 
130       this._checked = b;
131 
132       if (this._owner && this._owner.created) {
133          LVITEMW lvi;
134 
135          lvi.mask = LVIF_STATE;
136          lvi.stateMask = LVIS_STATEIMAGEMASK;
137          lvi.state = cast(LPARAM)(b ? 2 : 1) << 12; //Checked State
138 
139          this._owner.sendMessage(LVM_SETITEMSTATE, this.index, cast(LPARAM)&lvi);
140       }
141    }
142 
143    public final void addSubItem(string txt) {
144       if (this._parentItem) //E' un subitem, non fare niente.
145       {
146          return;
147       }
148 
149       if (!this._subItems) {
150          this._subItems = new Collection!(ListViewItem)();
151       }
152 
153       ListViewItem lvi = new ListViewItem(this._owner, this, txt, -1, false);
154       this._subItems.add(lvi);
155 
156       if (this._owner && this._owner.created) {
157          ListView.insertItem(lvi, true);
158       }
159    }
160 
161    @property public final ListViewItem[] subItems() {
162       if (this._subItems) {
163          return this._subItems.get();
164       }
165 
166       return null;
167    }
168 
169    @property public final ListView listView() {
170       return this._owner;
171    }
172 
173    @property package ListViewItem parentItem() {
174       return this._parentItem;
175    }
176 
177    package void removeSubItem(int idx) {
178       this._subItems.removeAt(idx);
179    }
180 
181    @property package int subitemIndex() {
182       if (this._parentItem is this) {
183          return 0; //Se è l'item principale ritorna 0.
184       } else if (!this._parentItem.subItems) {
185          return 1; //E' il primo subitem
186       } else if (this._owner && this._parentItem) {
187          int i = 0;
188 
189          foreach (ListViewItem lvi; this._parentItem.subItems) {
190             if (lvi is this) {
191                return i + 1;
192             }
193 
194             i++;
195          }
196       }
197 
198       return -1; //Non dovrebbe mai restituire -1
199    }
200 }
201 
202 class ListViewColumn {
203    private ColumnTextAlign _cta;
204    private ListView _owner;
205    private string _text;
206    private int _width;
207 
208    package this(ListView owner, string txt, int w, ColumnTextAlign cta) {
209       this._owner = owner;
210       this._text = txt;
211       this._width = w;
212       this._cta = cta;
213    }
214 
215    @property public int index() {
216       if (this._owner) {
217          int i = 0;
218 
219          foreach (ListViewColumn lvc; this._owner.columns) {
220             if (lvc is this) {
221                return i;
222             }
223 
224             i++;
225          }
226       }
227 
228       return -1;
229    }
230 
231    @property public string text() {
232       return this._text;
233    }
234 
235    @property public int width() {
236       return this._width;
237    }
238 
239    @property public ColumnTextAlign textAlign() {
240       return this._cta;
241    }
242 
243    @property public ListView listView() {
244       return this._owner;
245    }
246 }
247 
248 public alias ItemEventArgs!(ListViewItem) ListViewItemCheckedEventArgs;
249 
250 class ListView : OwnerDrawControl {
251    public Event!(Control, EventArgs) itemChanged;
252    public Event!(Control, ListViewItemCheckedEventArgs) itemChecked;
253 
254    private Collection!(ListViewColumn) _columns;
255    private Collection!(ListViewItem) _items;
256    private ListViewBits _lBits = ListViewBits.none;
257    private ListViewItem _selectedItem;
258    private ImageList _imgList;
259 
260    @property public final ImageList imageList() {
261       return this._imgList;
262    }
263 
264    @property public final void imageList(ImageList imgList) {
265       this._imgList = imgList;
266 
267       if (this.created) {
268          this.sendMessage(LVM_SETIMAGELIST, LVSIL_NORMAL, cast(LPARAM)imgList.handle);
269          this.sendMessage(LVM_SETIMAGELIST, LVSIL_SMALL, cast(LPARAM)imgList.handle);
270       }
271    }
272 
273    @property public final ViewStyle viewStyle() {
274       if (this.getStyle() & ViewStyle.largeIcon) {
275          return ViewStyle.largeIcon;
276       } else if (this.getStyle() & ViewStyle.smallIcon) {
277          return ViewStyle.smallIcon;
278       } else if (this.getStyle() & ViewStyle.list) {
279          return ViewStyle.list;
280       } else if (this.getStyle() & ViewStyle.report) {
281          return ViewStyle.report;
282       }
283 
284       assert(false, "Unknwown ListView Style");
285    }
286 
287    @property public final void viewStyle(ViewStyle vs) {
288       /* Remove flickering in Report Mode */
289       ListView.setBit(this._cBits, ControlBits.doubleBuffered, vs is ViewStyle.report);
290 
291       this.setStyle(vs, true);
292    }
293 
294    @property public final bool fullRowSelect() {
295       return cast(bool)(this._lBits & ListViewBits.fullRowSelect);
296    }
297 
298    @property public final void fullRowSelect(bool b) {
299       this._lBits |= ListViewBits.fullRowSelect;
300 
301       if (this.created) {
302          this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT,
303                b ? LVS_EX_FULLROWSELECT : 0);
304       }
305    }
306 
307    @property public final bool gridLines() {
308       return cast(bool)(this._lBits & ListViewBits.gridLines);
309    }
310 
311    @property public final void gridLines(bool b) {
312       this._lBits |= ListViewBits.gridLines;
313 
314       if (this.created) {
315          this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_GRIDLINES, b ? LVS_EX_GRIDLINES : 0);
316       }
317    }
318 
319    @property public final bool checkBoxes() {
320       return cast(bool)(this._lBits & ListViewBits.checkBoxes);
321    }
322 
323    @property public final void checkBoxes(bool b) {
324       this._lBits |= ListViewBits.checkBoxes;
325 
326       if (this.created) {
327          this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_CHECKBOXES, b
328                ? LVS_EX_CHECKBOXES : 0);
329       }
330    }
331 
332    @property public final ListViewItem selectedItem() {
333       return this._selectedItem;
334    }
335 
336    public final ListViewColumn addColumn(string txt, int w,
337          ColumnTextAlign cta = ColumnTextAlign.left) {
338       if (!this._columns) {
339          this._columns = new Collection!(ListViewColumn)();
340       }
341 
342       ListViewColumn lvc = new ListViewColumn(this, txt, w, cta);
343       this._columns.add(lvc);
344 
345       if (this.created) {
346          ListView.insertColumn(lvc);
347       }
348 
349       return lvc;
350    }
351 
352    public final void removeColumn(int idx) {
353       this._columns.removeAt(idx);
354 
355       /*
356 		 * Rimuovo tutti gli items nella colonna rimossa
357 		 */
358 
359       if (this._items) {
360          if (idx) {
361             foreach (ListViewItem lvi; this._items) {
362                lvi.removeSubItem(idx - 1); //Subitems iniziano da 0 nelle DGui e da 1 su Windows.
363             }
364          } else {
365             //TODO: Gestire caso "Rimozione colonna 0".
366          }
367       }
368 
369       if (this.created) {
370          this.sendMessage(LVM_DELETECOLUMN, idx, 0);
371       }
372    }
373 
374    public final ListViewItem addItem(string txt, int imgIdx = -1, bool checked = false) {
375       if (!this._items) {
376          this._items = new Collection!(ListViewItem)();
377       }
378 
379       ListViewItem lvi = new ListViewItem(this, txt, imgIdx, checked);
380       this._items.add(lvi);
381 
382       if (this.created) {
383          ListView.insertItem(lvi);
384       }
385 
386       return lvi;
387    }
388 
389    public final void removeItem(int idx) {
390       if (this._items) {
391          this._items.removeAt(idx);
392       }
393 
394       if (this.created) {
395          this.sendMessage(LVM_DELETEITEM, idx, 0);
396       }
397    }
398 
399    public final void clear() {
400       if (this._items) {
401          this._items.clear();
402       }
403 
404       if (this.created) {
405          this.sendMessage(LVM_DELETEALLITEMS, 0, 0);
406       }
407    }
408 
409    @property public final Collection!(ListViewItem) items() {
410       return this._items;
411    }
412 
413    @property public final Collection!(ListViewColumn) columns() {
414       return this._columns;
415    }
416 
417    package static void insertItem(ListViewItem item, bool subitem = false) {
418       /*
419 		 * Item: Item (or SubItem) to insert.
420 		 * Subitem = Is a SubItem?
421 		 */
422 
423       int idx = item.index;
424       LVITEMW lvi;
425 
426       lvi.mask = LVIF_TEXT | (!subitem ? (LVIF_IMAGE | LVIF_STATE | LVIF_PARAM) : 0);
427       lvi.iImage = !subitem ? item.imageIndex : -1;
428       lvi.iItem = !subitem ? idx : item.parentItem.index;
429       lvi.iSubItem = !subitem ? 0 : item.subitemIndex; //ListView's subitem starts from 1 (0 is the main item).
430       lvi.pszText = toUTFz!(wchar*)(item.text);
431       lvi.lParam = winCast!(LPARAM)(item);
432 
433       item.listView.sendMessage(!subitem ? LVM_INSERTITEMW : LVM_SETITEMW, 0, cast(LPARAM)&lvi);
434 
435       if (!subitem) {
436          if (item.listView.checkBoxes) //LVM_INSERTITEM doesn't handle CheckBoxes, use LVM_SETITEMSTATE
437          {
438             //Recycle the variable 'lvi'
439 
440             lvi.mask = LVIF_STATE;
441             lvi.stateMask = LVIS_STATEIMAGEMASK;
442             lvi.state = cast(LPARAM)(item.internalChecked ? 2 : 1) << 12; //Checked State
443             item.listView.sendMessage(LVM_SETITEMSTATE, idx, cast(LPARAM)&lvi);
444          }
445 
446          ListViewItem[] subItems = item.subItems;
447 
448          if (subItems) {
449             foreach (ListViewItem slvi; subItems) {
450                ListView.insertItem(slvi, true);
451             }
452          }
453       }
454    }
455 
456    private static void insertColumn(ListViewColumn col) {
457       LVCOLUMNW lvc;
458 
459       lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT;
460       lvc.cx = col.width;
461       lvc.fmt = col.textAlign;
462       lvc.pszText = toUTFz!(wchar*)(col.text);
463 
464       col.listView.sendMessage(LVM_INSERTCOLUMNW, col.listView._columns.length, cast(LPARAM)&lvc);
465    }
466 
467    protected override void createControlParams(ref CreateControlParams ccp) {
468       this.setStyle(LVS_ALIGNLEFT | LVS_ALIGNTOP | LVS_AUTOARRANGE | LVS_SHAREIMAGELISTS, true);
469 
470       /* WS_CLIPSIBLINGS | WS_CLIPCHILDREN: There is a SysHeader Component inside a list view in Report Mode */
471       if (this.getStyle() & ViewStyle.report) {
472          this.setStyle(WS_CLIPSIBLINGS | WS_CLIPCHILDREN, true);
473       }
474 
475       ccp.superclassName = WC_LISTVIEW;
476       ccp.className = WC_DLISTVIEW;
477       ccp.defaultBackColor = SystemColors.colorWindow;
478 
479       switch (this._drawMode) {
480       case OwnerDrawMode.fixed:
481          this.setStyle(LVS_OWNERDRAWFIXED, true);
482          break;
483 
484       case OwnerDrawMode.variable:
485          assert(false, "ListView: Owner Draw Variable Style not allowed");
486 
487       default:
488          break;
489       }
490 
491       //ListView.setBit(this._cBits, ControlBits.ORIGINAL_PAINT, true);
492       super.createControlParams(ccp);
493    }
494 
495    protected override void onReflectedMessage(ref Message m) {
496       switch (m.msg) {
497       case WM_NOTIFY: {
498             NMLISTVIEW* pNotify = cast(NMLISTVIEW*)m.lParam;
499 
500             if (pNotify && pNotify.iItem != -1) {
501                switch (pNotify.hdr.code) {
502                case LVN_ITEMCHANGED: {
503                      if (pNotify.uChanged & LVIF_STATE) {
504                         uint changedState = pNotify.uNewState ^ pNotify.uOldState;
505 
506                         if (pNotify.uNewState & LVIS_SELECTED) {
507                            this._selectedItem = this._items[pNotify.iItem];
508                            this.onSelectedItemChanged(EventArgs.empty);
509                         }
510 
511                         if ((changedState & 0x2000) || (changedState & 0x1000)) /* IF Checked || Unchecked THEN */ {
512                            scope ListViewItemCheckedEventArgs e = new ListViewItemCheckedEventArgs(
513                                  this._items[pNotify.iItem]);
514                            this.onItemChecked(e);
515                         }
516                      }
517                   }
518                   break;
519 
520                default:
521                   break;
522                }
523             }
524          }
525          break;
526 
527       default:
528          break;
529       }
530 
531       super.onReflectedMessage(m);
532    }
533 
534    protected override void onHandleCreated(EventArgs e) {
535       if (this._lBits & ListViewBits.gridLines) {
536          this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_GRIDLINES, LVS_EX_GRIDLINES);
537       }
538 
539       if (this._lBits & ListViewBits.fullRowSelect) {
540          this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT,
541                LVS_EX_FULLROWSELECT);
542       }
543 
544       if (this._lBits & ListViewBits.checkBoxes) {
545          this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES);
546       }
547 
548       if (this._imgList) {
549          this.sendMessage(LVM_SETIMAGELIST, LVSIL_NORMAL, cast(LPARAM)this._imgList.handle);
550          this.sendMessage(LVM_SETIMAGELIST, LVSIL_SMALL, cast(LPARAM)this._imgList.handle);
551       }
552 
553       if (this.getStyle() & ViewStyle.report) {
554          if (this._columns) {
555             foreach (ListViewColumn lvc; this._columns) {
556                ListView.insertColumn(lvc);
557             }
558          }
559 
560          /* Remove flickering in Report Mode */
561          ListView.setBit(this._cBits, ControlBits.doubleBuffered, true);
562       }
563 
564       if (this._items) {
565          foreach (ListViewItem lvi; this._items) {
566             ListView.insertItem(lvi);
567          }
568       }
569 
570       super.onHandleCreated(e);
571    }
572 
573    protected void onSelectedItemChanged(EventArgs e) {
574       this.itemChanged(this, e);
575    }
576 
577    protected void onItemChecked(ListViewItemCheckedEventArgs e) {
578       this.itemChecked(this, e);
579    }
580 }