Each line is implemented by an object of a subclass of ListViewLine, either
PickPackageLine, or (in catgeory view) PickCategoryLine

v2:
Defer constructing category tree until packagedb is available
Also handle an empty category tree
Remove unused bitmaps
Scoping of strings returned to LVN_GETDISPINFO
Avoid doubled 'all' category
Update some number of lines, rather than just the current one

v3:
Speed up calculating column widths, by caching the DC used
Don't bother recalculating column widths when obsolete packages are
shown/hidden, just allow for obsolete packages always (which makes hardly
any difference)
Don't bother calculating column widths separately for category view since
they are now always the same.

v4:
Store current category action in CategoryTree
Recurse category action onto packages

This slightly changes the behaviour: previously, a category action only
effected packages which matched the name search filter.  Now all packages in
contained by the category are effected.

v5:
When updating, turn off redraw before emptying listview
Make headers a parameter to init
Don't leak contents
Only resize columns once
Remove ListView OnMessage as it has nothing to do
Never use a column width less than minimum
Use LVS_SINGLESEL
Factor out string cache for re-use
Preserve focused row over ListvIew::setContents()
---
 ListView.cc         | 308 +++++++++++++++
 ListView.h          |  73 ++++
 Makefile.am         |  11 +-
 PickCategoryLine.cc | 135 +------
 PickCategoryLine.h  |  73 +---
 PickLine.h          |  47 ---
 PickPackageLine.cc  | 127 +++----
 PickPackageLine.h   |  25 +-
 PickView.cc         | 888 ++++++++------------------------------------
 PickView.h          | 217 ++++++-----
 check-na.bmp        | Bin 106 -> 0 bytes
 check-no.bmp        | Bin 106 -> 0 bytes
 check-yes.bmp       | Bin 106 -> 0 bytes
 choose-spin.bmp     | Bin 106 -> 0 bytes
 choose.cc           |  71 ++--
 choose.h            |   6 +-
 main.cc             |   2 +-
 res.rc              |  17 +-
 resource.h          |  11 -
 tree-minus.bmp      | Bin 106 -> 0 bytes
 tree-plus.bmp       | Bin 106 -> 0 bytes
 21 files changed, 799 insertions(+), 1212 deletions(-)
 create mode 100644 ListView.cc
 create mode 100644 ListView.h
 delete mode 100644 PickLine.h
 delete mode 100644 check-na.bmp
 delete mode 100644 check-no.bmp
 delete mode 100644 check-yes.bmp
 delete mode 100644 choose-spin.bmp
 delete mode 100644 tree-minus.bmp
 delete mode 100644 tree-plus.bmp

diff --git a/ListView.cc b/ListView.cc
new file mode 100644
index 0000000..97ee44c
--- /dev/null
+++ b/ListView.cc
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2016 Jon Turney
+ *
+ *     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; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#include "ListView.h"
+#include "LogSingleton.h"
+
+#include <commctrl.h>
+
+// ---------------------------------------------------------------------------
+// implements class ListView
+//
+// ListView Common Control
+// ---------------------------------------------------------------------------
+
+void
+ListView::init(HWND parent, int id, HeaderList headers)
+{
+  hWndParent = parent;
+
+  // locate the listview control
+  hWndListView = ::GetDlgItem(parent, id);
+
+  // configure the listview control
+  SendMessage(hWndListView, CCM_SETVERSION, 6, 0);
+
+  ListView_SetExtendedListViewStyle(hWndListView,
+                                    LVS_EX_COLUMNSNAPPOINTS | // use cxMin
+                                    LVS_EX_FULLROWSELECT |
+                                    LVS_EX_GRIDLINES |
+                                    LVS_EX_HEADERDRAGDROP);   // headers can 
be re-ordered
+
+  // give the header control a border
+  HWND hWndHeader = ListView_GetHeader(hWndListView);
+  SetWindowLongPtr(hWndHeader, GWL_STYLE,
+                   GetWindowLongPtr(hWndHeader, GWL_STYLE) | WS_BORDER);
+
+  // ensure an initial item exists for width calculations...
+  LVITEM lvi;
+  lvi.mask = LVIF_TEXT;
+  lvi.iItem = 0;
+  lvi.iSubItem = 0;
+  lvi.pszText = const_cast <char *> ("Working...");
+  ListView_InsertItem(hWndListView, &lvi);
+
+  // populate with columns
+  initColumns(headers);
+}
+
+void
+ListView::initColumns(HeaderList headers_)
+{
+  // store HeaderList for later use
+  headers = headers_;
+
+  // create the columns
+  LVCOLUMN lvc;
+  lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
+
+  int i;
+  for (i = 0; headers[i].text != 0; i++)
+    {
+      lvc.iSubItem = i;
+      lvc.pszText = const_cast <char *> (headers[i].text);
+      lvc.cx = 100;
+      lvc.fmt = headers[i].fmt;
+
+      ListView_InsertColumn(hWndListView, i, &lvc);
+    }
+
+  // now do some width calculations
+  for (i = 0; headers[i].text != 0; i++)
+    {
+      headers[i].width = 0;
+
+      ListView_SetColumnWidth(hWndListView, i, LVSCW_AUTOSIZE_USEHEADER);
+      headers[i].hdr_width = ListView_GetColumnWidth(hWndListView, i);
+    }
+}
+
+void
+ListView::noteColumnWidthStart()
+{
+  dc = GetDC (hWndListView);
+
+  // we must set the font of the DC here, otherwise the width calculations
+  // will be off because the system will use the wrong font metrics
+  HANDLE sysfont = GetStockObject (DEFAULT_GUI_FONT);
+  SelectObject (dc, sysfont);
+
+  int i;
+  for (i = 0; headers[i].text != 0; i++)
+    {
+      headers[i].width = 0;
+    }
+}
+
+void
+ListView::noteColumnWidth(int col_num, const std::string& string)
+{
+  SIZE s = { 0, 0 };
+
+  // A margin of 3*GetSystemMetrics(SM_CXEDGE) is used at each side of the
+  // header text.
+  int addend = 2*3*GetSystemMetrics(SM_CXEDGE);
+
+  if (string.size())
+    GetTextExtentPoint32 (dc, string.c_str(), string.size(), &s);
+
+  int width = addend + s.cx;
+
+  if (width > headers[col_num].width)
+    headers[col_num].width = width;
+}
+
+void
+ListView::noteColumnWidthEnd()
+{
+  ReleaseDC(hWndListView, dc);
+}
+
+void
+ListView::resizeColumns(void)
+{
+  // ensure the last column stretches all the way to the right-hand side of the
+  // listview control
+  int i;
+  int total = 0;
+  for (i = 0; headers[i].text != 0; i++)
+    total = total + headers[i].width;
+
+  RECT r;
+  GetClientRect(hWndListView, &r);
+  int width = r.right - r.left;
+
+  if (total < width)
+    headers[i-1].width += width - total;
+
+  // size each column
+  LVCOLUMN lvc;
+  lvc.mask = LVCF_WIDTH | LVCF_MINWIDTH;
+  for (i = 0; headers[i].text != 0; i++)
+    {
+      lvc.iSubItem = i;
+      lvc.cx = (headers[i].width < headers[i].hdr_width) ? 
headers[i].hdr_width : headers[i].width;
+      lvc.cxMin = headers[i].hdr_width;
+#if DEBUG
+      Log (LOG_BABBLE) << "resizeColumns: " << i << " cx " << lvc.cx << " 
cxMin " << lvc.cxMin <<endLog;
+#endif
+
+      ListView_SetColumn(hWndListView, i, &lvc);
+    }
+}
+
+void
+ListView::setContents(ListViewContents *_contents)
+{
+  contents = _contents;
+
+  // disable redrawing of ListView
+  // (otherwise it will redraw every time a row is added, which makes this 
very slow)
+  SendMessage(hWndListView, WM_SETREDRAW, FALSE, 0);
+
+  // preserve focus/selection
+  int iRow = ListView_GetSelectionMark(hWndListView);
+
+  empty();
+
+  size_t i;
+  for (i = 0; i < contents->size();  i++)
+    {
+      LVITEM lvi;
+      lvi.mask = LVIF_TEXT;
+      lvi.iItem = i;
+      lvi.iSubItem = 0;
+      lvi.pszText = LPSTR_TEXTCALLBACK;
+
+      ListView_InsertItem(hWndListView, &lvi);
+    }
+
+  if (iRow >= 0)
+    {
+      ListView_SetItemState(hWndListView, iRow, LVNI_SELECTED | LVNI_FOCUSED, 
LVNI_SELECTED | LVNI_FOCUSED);
+      ListView_EnsureVisible(hWndListView, iRow, false);
+    }
+
+  // enable redrawing of ListView and redraw
+  SendMessage(hWndListView, WM_SETREDRAW, TRUE, 0);
+  RedrawWindow(hWndListView, NULL, NULL, RDW_ERASE | RDW_FRAME | 
RDW_INVALIDATE | RDW_ALLCHILDREN);
+}
+
+// Helper class: The char * pointer we hand back needs to remain valid for some
+// time after OnNotify returns, when the std::string we have retrieved has gone
+// out of scope, so a static instance of this class maintains a local cache.
+class StringCache
+{
+public:
+  StringCache() : cache(NULL), cache_size(0) { }
+  StringCache & operator = (const std::string & s)
+  {
+    if ((s.length() + 1) > cache_size)
+      {
+        cache_size = s.length() + 1;
+        cache = (char *)realloc(cache, cache_size);
+      }
+    strcpy(cache, s.c_str());
+    return *this;
+  }
+  operator char *() const
+  {
+    return cache;
+  }
+private:
+  char *cache;
+  size_t cache_size;
+};
+
+bool
+ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
+{
+#if DEBUG
+  Log (LOG_BABBLE) << "ListView::OnNotify id:" << pNmHdr->idFrom << " hwnd:" 
<< pNmHdr->hwndFrom << " code:" << (int)pNmHdr->code << endLog;
+#endif
+
+  switch (pNmHdr->code)
+  {
+  case LVN_GETDISPINFO:
+    {
+      NMLVDISPINFO *pNmLvDispInfo = (NMLVDISPINFO *)pNmHdr;
+#if DEBUG
+      Log (LOG_BABBLE) << "LVN_GETDISPINFO " << pNmLvDispInfo->item.iItem << 
endLog;
+#endif
+      if (contents)
+        {
+          int iRow = pNmLvDispInfo->item.iItem;
+          int iCol = pNmLvDispInfo->item.iSubItem;
+
+          static StringCache s;
+          s = (*contents)[iRow]->get_text(iCol);
+          pNmLvDispInfo->item.pszText = s;
+        }
+
+      return true;
+    }
+    break;
+
+  case LVN_GETEMPTYMARKUP:
+    {
+      NMLVEMPTYMARKUP *pNmMarkup = (NMLVEMPTYMARKUP*) pNmHdr;
+
+      MultiByteToWideChar(CP_UTF8, 0,
+                          empty_list_text, -1,
+                          pNmMarkup->szMarkup, L_MAX_URL_LENGTH);
+
+      *pResult = true;
+      return true;
+    }
+    break;
+
+  case NM_CLICK:
+    {
+      NMITEMACTIVATE *pNmItemAct = (NMITEMACTIVATE *) pNmHdr;
+#if DEBUG
+      Log (LOG_BABBLE) << "NM_CLICK: pnmitem->iItem " << pNmItemAct->iItem << 
" pNmItemAct->iSubItem " << pNmItemAct->iSubItem << endLog;
+#endif
+      int iRow = pNmItemAct->iItem;
+      int iCol = pNmItemAct->iSubItem;
+
+      if (iRow >= 0)
+        {
+          // Inform the item of the click
+          int update = (*contents)[iRow]->do_action(iCol);
+
+          // Update items, if needed
+          if (update > 0)
+            {
+              ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
+            }
+        }
+      return true;
+    }
+    break;
+  }
+
+  // We don't care.
+  return false;
+}
+
+void
+ListView::empty(void)
+{
+  ListView_DeleteAllItems(hWndListView);
+}
+
+void
+ListView::setEmptyText(const char *text)
+{
+  empty_list_text = text;
+}
diff --git a/ListView.h b/ListView.h
new file mode 100644
index 0000000..d339011
--- /dev/null
+++ b/ListView.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2016 Jon Turney
+ *
+ *     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; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#ifndef SETUP_LISTVIEW_H
+#define SETUP_LISTVIEW_H
+
+#include "win32.h"
+#include <vector>
+
+// ---------------------------------------------------------------------------
+// interface to class ListView
+//
+// ListView Common Control
+// ---------------------------------------------------------------------------
+
+class ListViewLine
+{
+ public:
+  virtual ~ListViewLine() {};
+  virtual const std::string get_text(int col) const = 0;
+  virtual int do_action(int col) = 0;
+};
+
+typedef std::vector<ListViewLine *> ListViewContents;
+
+class ListView
+{
+ public:
+  class Header
+  {
+  public:
+    const char *text;
+    int fmt;
+    int width;
+    int hdr_width;
+  };
+  typedef Header *HeaderList;
+
+  void init(HWND parent, int id, HeaderList headers);
+
+  void noteColumnWidthStart();
+  void noteColumnWidth(int col_num, const std::string& string);
+  void noteColumnWidthEnd();
+  void resizeColumns(void);
+
+  void setContents(ListViewContents *contents);
+  void setEmptyText(const char *text);
+
+  bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult);
+
+ private:
+  HWND hWndParent;
+  HWND hWndListView;
+  HDC dc;
+  ListViewContents *contents;
+  HeaderList headers;
+  const char *empty_list_text;
+
+  void initColumns(HeaderList hl);
+  void empty(void);
+};
+
+#endif /* SETUP_LISTVIEW_H */
diff --git a/Makefile.am b/Makefile.am
index 7bd7c57..bce4c8c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -42,17 +42,11 @@ EXTRA_DIST = \
        CONTRIBUTORS \
        COPYING \
        bootstrap.sh \
-       check-na.bmp \
-       check-no.bmp \
-       check-yes.bmp \
-       choose-spin.bmp \
        cygwin.ico \
        cygwin-setup.ico \
        cygwin-terminal.ico \
        setup.exe.manifest \
-       setup64.exe.manifest \
-       tree-minus.bmp \
-       tree-plus.bmp
+       setup64.exe.manifest
 
 # iniparse.hh is generated from iniparse.yy via bison -d, so it needs to be
 # included here for proper tracking (but not iniparse.cc, since automake
@@ -172,6 +166,8 @@ inilint_SOURCES = \
        KeysSetting.h \
        libsolv.cc \
        libsolv.h \
+       ListView.cc \
+       ListView.h \
        localdir.cc \
        localdir.h \
        LogFile.cc \
@@ -207,7 +203,6 @@ inilint_SOURCES = \
        PackageTrust.h \
        PickCategoryLine.cc \
        PickCategoryLine.h \
-       PickLine.h \
        PickPackageLine.cc \
        PickPackageLine.h \
        PickView.cc \
diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc
index e428419..6737454 100644
--- a/PickCategoryLine.cc
+++ b/PickCategoryLine.cc
@@ -16,134 +16,37 @@
 #include "PickCategoryLine.h"
 #include "package_db.h"
 #include "PickView.h"
+#include "window.h"
 
-void
-PickCategoryLine::empty (void)
+const std::string
+PickCategoryLine::get_text (int col_num) const
 {
-  while (bucket.size ())
+  if (col_num == pkgname_col)
     {
-      PickLine *line = *bucket.begin ();
-      delete line;
-      bucket.erase (bucket.begin ());
+      std::string s = (cat_tree->collapsed() ? "[+] " : "[-] ") + 
cat_tree->category().first;
+      return s;
     }
-}
-
-void
-PickCategoryLine::paint (HDC hdc, HRGN hUpdRgn, int x, int y, int row, int 
show_cat)
-{
-  int r = y + row * theView.row_height;
-  if (show_label)
-    {
-      int x2 = x + theView.headers[theView.cat_col].x + HMARGIN / 2 + depth * 
TREE_INDENT;
-      int by = r + (theView.tm.tmHeight / 2) - 5;
-
-      // draw the '+' or '-' box
-      theView.DrawIcon (hdc, x2, by, (collapsed ? theView.bm_treeplus : 
theView.bm_treeminus));
-
-      // draw the category name
-      TextOut (hdc, x2 + 11 + ICON_MARGIN, r, cat.first.c_str(), 
cat.first.size());
-      if (!labellength)
-       {
-         SIZE s;
-         GetTextExtentPoint32 (hdc, cat.first.c_str(), cat.first.size(), &s);
-         labellength = s.cx;
-       }
-      
-      // draw the 'spin' glyph
-      spin_x = x2 + 11 + ICON_MARGIN + labellength + ICON_MARGIN;
-      theView.DrawIcon (hdc, spin_x, by, theView.bm_spin);
-      
-      // draw the caption ('Default', 'Install', etc)
-      TextOut (hdc, spin_x + SPIN_WIDTH + ICON_MARGIN, r, 
-               packagemeta::action_caption (current_default),
-               strlen (packagemeta::action_caption (current_default)));
-      row++;
-    }
-  if (collapsed)
-    return;
-  
-  // are the siblings containers?
-  if (bucket.size () && bucket[0]->IsContainer ())
-    {
-      for (size_t n = 0; n < bucket.size (); n++)
-        {
-          bucket[n]->paint (hdc, hUpdRgn, x, y, row, show_cat);
-          row += bucket[n]->itemcount ();
-        }
-    }
-  else
+  else if (col_num == new_col)
     {
-      // calculate the maximum y value we expect for this group of lines
-      int max_y = y + (row + bucket.size ()) * theView.row_height;
-    
-      // paint all contained rows, columnwise
-      for (int i = 0; theView.headers[i].text; i++)
-        {
-          RECT r;
-          r.left = x + theView.headers[i].x;
-          r.right = r.left + theView.headers[i].width;
-    
-          // set up a clipping mask if necessary
-          if (theView.headers[i].needs_clip)
-            IntersectClipRect (hdc, r.left, y, r.right, max_y);
-    
-          // draw each row in this column
-          for (unsigned int n = 0; n < bucket.size (); n++)
-            {
-              // test for visibility
-              r.top = y + ((row + n) * theView.row_height);
-              r.bottom = r.top + theView.row_height;      
-              if (RectVisible (hdc, &r) != 0)
-                bucket[n]->paint (hdc, hUpdRgn, (int)r.left, (int)r.top, i, 
show_cat);
-            }
-    
-          // restore original clipping area
-          if (theView.headers[i].needs_clip)
-            SelectClipRgn (hdc, hUpdRgn);
-        }
+      return packagemeta::action_caption (cat_tree->action());
     }
+  return "";
 }
 
 int
-PickCategoryLine::click (int const myrow, int const ClickedRow, int const x)
+PickCategoryLine::do_action(int col_num)
 {
-  if (myrow == ClickedRow && show_label)
+  if (col_num == pkgname_col)
     {
-      if ((size_t) x >= spin_x)
-       {
-         current_default = (packagemeta::_actions)((current_default + 1) % 4);
-         return set_action (current_default);
-       }
-      else
-       {
-         collapsed = !collapsed;
-         int accum_row = 0;
-         for (size_t n = 0; n < bucket.size (); ++n)
-           accum_row += bucket[n]->itemcount ();
-         return collapsed ? accum_row : -accum_row;
-       }
+      cat_tree->collapsed() = ! cat_tree->collapsed();
+      theView.refresh();
     }
-  else
+  else if (col_num == new_col)
     {
-      int accum_row = myrow + (show_label ? 1 : 0);
-      for (size_t n = 0; n < bucket.size (); ++n)
-       {
-         if (accum_row + bucket[n]->itemcount () > ClickedRow)
-           return bucket[n]->click (accum_row, ClickedRow, x);
-         accum_row += bucket[n]->itemcount ();
-       }
-      return 0;
+      theView.GetParent ()->SetBusy ();
+      int u = cat_tree->do_action((packagemeta::_actions)((cat_tree->action() 
+ 1) % 4), theView.deftrust);
+      theView.GetParent ()->ClearBusy ();
+      return u;
     }
-}
-
-int 
-PickCategoryLine::set_action (packagemeta::_actions action)
-{
-  theView.GetParent ()->SetBusy ();
-  current_default = action;
-  int accum_diff = 0;
-  for (size_t n = 0; n < bucket.size (); n++)
-      accum_diff += bucket[n]->set_action (current_default);
-  theView.GetParent ()->ClearBusy ();
-  return accum_diff;
+  return 1;
 }
diff --git a/PickCategoryLine.h b/PickCategoryLine.h
index dcffbac..9423eb8 100644
--- a/PickCategoryLine.h
+++ b/PickCategoryLine.h
@@ -16,75 +16,28 @@
 #ifndef SETUP_PICKCATEGORYLINE_H
 #define SETUP_PICKCATEGORYLINE_H
 
-
-class PickView;
-#include <vector>
-#include "PickLine.h"
 #include "package_meta.h"
+#include "ListView.h"
+#include "PickView.h"
 
-class PickCategoryLine:public PickLine
+class PickCategoryLine: public ListViewLine
 {
 public:
-  PickCategoryLine (PickView & aView, Category & _cat, size_t thedepth = 0, 
bool aBool =
-                     true, bool aBool2 =
-                     true):PickLine (_cat.first),
-    current_default (packagemeta::Default_action), cat (_cat), labellength (0),
-    depth (thedepth), theView (aView)
+  PickCategoryLine (PickView & aView, CategoryTree * _tree) :
+    cat_tree (_tree),
+    theView (aView)
   {
-    if (aBool)
-      {
-       collapsed = true;
-       show_label = true;
-      }
-    else
-      {
-       collapsed = false;
-       show_label = aBool2;
-      }
   };
   ~PickCategoryLine ()
   {
-    empty ();
-  }
-  void ShowLabel (bool aBool = true)
-  {
-    show_label = aBool;
-    if (!show_label)
-      collapsed = false;
-  }
-  virtual void paint (HDC hdc, HRGN hUpdRgn, int x, int y, int row, int 
show_cat);
-  virtual int click (int const myrow, int const ClickedRow, int const x);
-  virtual int itemcount () const
-  {
-    if (collapsed)
-      return 1;
-    int t = show_label ? 1 : 0;
-    for (size_t n = 0; n < bucket.size (); ++n)
-        t += bucket[n]->itemcount ();
-      return t;
-  };
-  virtual bool IsContainer (void) const
-  {
-    return true;
-  }
-  virtual void insert (PickLine & aLine)
-  {
-    bucket.push_back (&aLine);
   }
-  void empty ();
-  virtual int set_action (packagemeta::_actions);
+
+  const std::string get_text(int col) const;
+  int do_action(int col);
+
 private:
-  packagemeta::_actions
-  current_default;
-  Category & cat;
-  bool collapsed;
-  bool show_label;
-  size_t labellength;
-  size_t spin_x;    // x-coord where the spin button starts
-  size_t depth;
-  PickCategoryLine (PickCategoryLine const &);
-  PickCategoryLine & operator= (PickCategoryLine const &);
-  std::vector < PickLine * > bucket;
-  PickView& theView;
+  CategoryTree * cat_tree;
+  PickView & theView;
 };
+
 #endif /* SETUP_PICKCATEGORYLINE_H */
diff --git a/PickLine.h b/PickLine.h
deleted file mode 100644
index 7cf84cb..0000000
--- a/PickLine.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (c) 2002 Robert Collins.
- *
- *     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; either version 2 of the License, or
- *     (at your option) any later version.
- *
- *     A copy of the GNU General Public License can be found at
- *     http://www.gnu.org/
- *
- * Written by Robert Collins <robe...@hotmail.com>
- *
- */
-
-#ifndef SETUP_PICKLINE_H
-#define SETUP_PICKLINE_H
-
-#include "win32.h"
-#include "package_meta.h"
-
-class PickLine
-{
-public:
-  virtual void paint (HDC hdc, HRGN hUpdRgn, int x, int y, int col_num, int 
show_cat) = 0;
-  virtual int click (int const myrow, int const ClickedRow, int const x) = 0;
-  virtual int set_action (packagemeta::_actions) = 0;
-  virtual int itemcount () const = 0;
-  // this may indicate bad inheritance model.
-  virtual bool IsContainer (void) const = 0;
-  virtual void insert (PickLine &) = 0;
-  const std::string key;
-  virtual ~ PickLine ()
-  {
-  };
-protected:
-  PickLine ()
-  {
-  };
-  PickLine (const std::string& aKey) : key (aKey)
-  {
-  };
-  PickLine (PickLine const &);
-  PickLine & operator= (PickLine const &);
-};
-
-#endif /* SETUP_PICKLINE_H */
diff --git a/PickPackageLine.cc b/PickPackageLine.cc
index 2caeafe..b348ab0 100644
--- a/PickPackageLine.cc
+++ b/PickPackageLine.cc
@@ -17,70 +17,54 @@
 #include "PickView.h"
 #include "package_db.h"
 
-void
-PickPackageLine::paint (HDC hdc, HRGN unused, int x, int y, int col_num, int 
show_cat)
+const std::string
+PickPackageLine::get_text(int col_num) const
 {
-  int rb = y + theView.tm.tmHeight;
-  int by = rb - 11; // top of box images
-  std::string s;
-
-  if (col_num == theView.current_col && pkg.installed)
+  if (col_num == pkgname_col)
+    {
+      return pkg.name;
+    }
+  else if (col_num == current_col)
     {
-      TextOut (hdc, x + HMARGIN/2, y, pkg.installed.Canonical_version 
().c_str(),
-               pkg.installed.Canonical_version ().size());
+      return pkg.installed.Canonical_version ();
     }
-  else if (col_num == theView.new_col)
+  else if (col_num == new_col)
     {
-      // TextOut (hdc, x + HMARGIN/2 + NEW_COL_SIZE_SLOP, y, s.c_str(), 
s.size());
-      // theView.DrawIcon (hdc, x + HMARGIN/2 + ICON_MARGIN/2 + RTARROW_WIDTH, 
by, theView.bm_spin);
-      TextOut (hdc, x + HMARGIN/2 + ICON_MARGIN/2 + SPIN_WIDTH , y,
-            pkg.action_caption ().c_str(), pkg.action_caption ().size());
-      theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_spin);
+      return pkg.action_caption ();
     }
-  else if (col_num == theView.bintick_col)
+  else if (col_num == bintick_col)
     {
+      const char *bintick = "?";
       if (/* uninstall or skip */ !pkg.desired ||
           /* current version */ pkg.desired == pkg.installed ||
           /* no source */ !pkg.desired.accessible())
-        theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkna);
+        bintick = "n/a";
       else if (pkg.picked())
-        theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkyes);
+        bintick = "yes";
       else
-        theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkno);
+        bintick = "no";
+
+      return bintick;
     }
-  else if (col_num == theView.srctick_col)
+  else if (col_num == srctick_col)
     {
+      const char *srctick = "?";
       if ( /* uninstall */ !pkg.desired ||
-
-#if 0
-          /* note: I'm not sure what the logic here is.  With this following
-             check enabled, clicking on the "source" box for a package that
-             is already installed results it in showing "n/a", instead of a
-             cross-box.  That seems very unintuitive, it should show a cross-
-             box to indicate that the source is going to be downloaded and
-             unpacked.  Disabling this, but leaving the code as reference
-             in case there is some reason I'm missing for having it. --b.d.  */
-          /* source only */ (!pkg.desired.picked()
-                        && pkg.desired.sourcePackage().picked() && pkg.desired 
== pkg.installed) ||
-#endif
-          /* when no source mirror available */
-          !pkg.desired.sourcePackage().accessible())
-        theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkna);
+           /* when no source mirror available */
+           !pkg.desired.sourcePackage().accessible())
+        srctick = "n/a";
       else if (pkg.srcpicked())
-        theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkyes);
+        srctick = "yes";
       else
-        theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkno);
+        srctick = "no";
+
+      return srctick;
     }
-  else if (col_num == theView.cat_col)
+  else if (col_num == cat_col)
     {
-      /* shows "first" category - do we want to show any? */
-      if (pkg.categories.size () && show_cat)
-        {
-          s = pkg.getReadableCategoryList();
-          TextOut (hdc, x + HMARGIN / 2, y, s.c_str(), s.size());
-        }
+      return pkg.getReadableCategoryList();
     }
-  else if (col_num == theView.size_col)
+  else if (col_num == size_col)
     {
       int sz = 0;
       packageversion picked;
@@ -103,62 +87,49 @@ PickPackageLine::paint (HDC hdc, HRGN unused, int x, int 
y, int col_num, int sho
         sz += picked.sourcePackage().source()->size;
 
       /* If size still 0, size must be unknown.  */
-      s = (sz == 0) ? "?" : format_1000s((sz+1023)/1024) + "k";
-      SIZE tw;
-      GetTextExtentPoint32 (hdc, s.c_str(), s.size(), &tw);
-      int cw = theView.headers[col_num].width - HMARGIN - tw.cx;
-      TextOut (hdc, x + cw + HMARGIN / 2, y, s.c_str(), s.size());
+      std::string size = (sz == 0) ? "?" : format_1000s((sz+1023)/1024) + "k";
+
+      return size;
     }
-  else if (col_num == theView.pkg_col)
+  else if (col_num == pkg_col)
     {
-      s = pkg.name;
-      if (pkg.SDesc ().size())
-        s += std::string(": ") + std::string(pkg.SDesc());
-      TextOut (hdc, x + HMARGIN / 2, y, s.c_str(), s.size());
+      return pkg.SDesc();
     }
+
+  return "unknown";
 }
 
 int
-PickPackageLine::click (int const myrow, int const ClickedRow, int const x)
+PickPackageLine::do_action(int col_num)
 {
-  // assert (myrow == ClickedRow);
-  if (x >= theView.headers[theView.new_col].x - HMARGIN / 2
-      && x <= theView.headers[theView.new_col + 1].x - HMARGIN / 2)
+  if (col_num == new_col)
     {
       pkg.set_action (theView.deftrust);
-      return 0;
+      return 1;
     }
-  if (x >= theView.headers[theView.bintick_col].x - HMARGIN / 2
-      && x <= theView.headers[theView.bintick_col + 1].x - HMARGIN / 2)
+
+  if (col_num == bintick_col)
     {
       if (pkg.desired.accessible ())
-       pkg.pick (!pkg.picked ());
+        pkg.pick (!pkg.picked ());
     }
-  else if (x >= theView.headers[theView.srctick_col].x - HMARGIN / 2
-          && x <= theView.headers[theView.srctick_col + 1].x - HMARGIN / 2)
+  else if (col_num == srctick_col)
     {
       if (pkg.desired.sourcePackage ().accessible ())
-       pkg.srcpick (!pkg.srcpicked ());
+        pkg.srcpick (!pkg.srcpicked ());
     }
 
-  /* Unchecking binary while source is unchecked or vice versa is equivalent
-     to uninstalling.  It's essential to set desired correctly, otherwise the
+  /* Unchecking binary while source is unchecked or vice versa is equivalent to
+     uninstalling.  It's essential to set desired correctly, otherwise the
      package gets uninstalled without visual feedback to the user.  The package
      will not even show up in the "Pending" view! */
-  if ((x >= theView.headers[theView.bintick_col].x - HMARGIN / 2
-      && x <= theView.headers[theView.bintick_col + 1].x - HMARGIN / 2) ||
-      (x >= theView.headers[theView.srctick_col].x - HMARGIN / 2
-       && x <= theView.headers[theView.srctick_col + 1].x - HMARGIN / 2))
+  if ((col_num == bintick_col) || (col_num == srctick_col))
     {
       if (!pkg.picked () && !pkg.srcpicked ())
         pkg.desired = packageversion ();
+
+      return 1;
     }
 
   return 0;
 }
-
-int PickPackageLine::set_action (packagemeta::_actions action)
-{
-  pkg.set_action (action, pkg.trustp (true, theView.deftrust));
-  return 1;
-}
diff --git a/PickPackageLine.h b/PickPackageLine.h
index 612bf38..a8c3c0d 100644
--- a/PickPackageLine.h
+++ b/PickPackageLine.h
@@ -17,29 +17,20 @@
 #define SETUP_PICKPACKAGELINE_H
 
 class PickView;
+
 #include "package_meta.h"
-#include "PickLine.h"
+#include "ListView.h"
 
-class PickPackageLine:public PickLine
+class PickPackageLine: public ListViewLine
 {
 public:
-  PickPackageLine (PickView &aView, packagemeta & apkg):PickLine (apkg.key), 
pkg (apkg), theView (aView)
-  {
-  };
-  virtual void paint (HDC hdc, HRGN unused, int x, int y, int col_num, int 
show_cat);
-  virtual int click (int const myrow, int const ClickedRow, int const x);
-  virtual int itemcount () const
-  {
-    return 1;
-  }
-  virtual bool IsContainer (void) const
-  {
-    return false;
-  }
-  virtual void insert (PickLine &)
+  PickPackageLine (PickView &aView, packagemeta & apkg) :
+    pkg (apkg),
+    theView (aView)
   {
   };
-  virtual int set_action (packagemeta::_actions);
+  const std::string get_text(int col) const;
+  int do_action(int col);
 private:
   packagemeta & pkg;
   PickView & theView;
diff --git a/PickView.cc b/PickView.cc
index 57e8f85..967d53b 100644
--- a/PickView.cc
+++ b/PickView.cc
@@ -14,12 +14,12 @@
  */
 
 #include "PickView.h"
+#include "PickPackageLine.h"
+#include "PickCategoryLine.h"
 #include <algorithm>
 #include <limits.h>
-#include <commctrl.h>
 #include <shlwapi.h>
-#include "PickPackageLine.h"
-#include "PickCategoryLine.h"
+
 #include "package_db.h"
 #include "dialog.h"
 #include "resource.h"
@@ -28,135 +28,25 @@
 #include "LogSingleton.h"
 #include "Exception.h"
 
-static PickView::Header pkg_headers[] = {
-  {"Current", 0, 0, true},
-  {"New", 0, 0, true},
-  {"Bin?", 0, 0, false},
-  {"Src?", 0, 0, false},
-  {"Categories", 0, 0, true},
-  {"Size", 0, 0, true},
-  {"Package", 0, 0, true},
-  {0, 0, 0, false}
-};
-
-static PickView::Header cat_headers[] = {
-  {"Category", 0, 0, true},
-  {"Current", 0, 0, true},
-  {"New", 0, 0, true},
-  {"Bin?", 0, 0, false},
-  {"Src?", 0, 0, false},
-  {"Size", 0, 0, true},
-  {"Package", 0, 0, true},
-  {0, 0, 0, false}
-};
-
-ATOM PickView::WindowClassAtom = 0;
-
-// DoInsertItem - inserts an item into a header control.
-// Returns the index of the new item.
-// hwndHeader - handle to the header control.
-// iInsertAfter - index of the previous item.
-// nWidth - width of the new item.
-// lpsz - address of the item string.
-static int
-DoInsertItem (HWND hwndHeader, int iInsertAfter, int nWidth, LPSTR lpsz)
-{
-  HDITEM hdi;
-  int index;
-
-  hdi.mask = HDI_TEXT | HDI_FORMAT | HDI_WIDTH;
-  hdi.pszText = lpsz;
-  hdi.cxy = nWidth;
-  hdi.cchTextMax = lstrlen (hdi.pszText);
-  hdi.fmt = HDF_LEFT | HDF_STRING;
-
-  index = SendMessage (hwndHeader, HDM_INSERTITEM,
-                       (WPARAM) iInsertAfter, (LPARAM) & hdi);
-
-  return index;
-}
-
-int
-PickView::set_header_column_order (views vm)
-{
-  if (vm == views::PackageFull || vm == views::PackagePending
-      || vm == views::PackageKeeps || vm == views::PackageSkips
-      || vm == views::PackageUserPicked)
-    {
-      headers = pkg_headers;
-      current_col = 0;
-      new_col = 1;
-      bintick_col = new_col + 1;
-      srctick_col = bintick_col + 1;
-      cat_col = srctick_col + 1;
-      size_col = cat_col + 1;
-      pkg_col = size_col + 1;
-      last_col = pkg_col;
-    }
-  else if (vm == views::Category)
-    {
-      headers = cat_headers;
-      cat_col = 0;
-      current_col = 1;
-      new_col = current_col + 1;
-      bintick_col = new_col + 1;
-      srctick_col = bintick_col + 1;
-      size_col = srctick_col + 1;
-      pkg_col = size_col + 1;
-      last_col = pkg_col;
-    }
-  else
-    return -1;
-  return last_col;
-}
-
-void
-PickView::set_headers ()
-{
-  if (set_header_column_order (view_mode) == -1)
-    return;
-  while (int n = SendMessage (listheader, HDM_GETITEMCOUNT, 0, 0))
-    {
-      SendMessage (listheader, HDM_DELETEITEM, n - 1, 0);
-    }
-  int i;
-  for (i = 0; i <= last_col; i++)
-    DoInsertItem (listheader, i, headers[i].width, (char *) headers[i].text);
-}
-
-void
-PickView::note_width (PickView::Header *hdrs, HDC dc,
-                      const std::string& string, int addend, int column)
-{
-  SIZE s = { 0, 0 };
-
-  if (string.size())
-    GetTextExtentPoint32 (dc, string.c_str(), string.size(), &s);
-  if (hdrs[column].width < s.cx + addend)
-    hdrs[column].width = s.cx + addend;
-}
-
 void
 PickView::setViewMode (views mode)
 {
   view_mode = mode;
-  set_headers ();
   packagedb db;
 
-  contents.empty ();
+  size_t i;
+  for (i = 0; i < contents.size();  i++)
+    {
+      delete contents[i];
+    }
+  contents.clear();
+
   if (view_mode == PickView::views::Category)
     {
-      contents.ShowLabel (true);
-      /* start collapsed. TODO: make this a chooser flag */
-      for (packagedb::categoriesType::iterator n =
-            packagedb::categories.begin(); n != packagedb::categories.end();
-            ++n)
-        insert_category (&*n, (*n).first.c_str()[0] == '.'
-                               ? CATEGORY_EXPANDED : CATEGORY_COLLAPSED);
+      insert_category (cat_tree_root);
     }
   else
     {
-      contents.ShowLabel (false);
       // iterate through every package
       for (packagedb::packagecollection::iterator i = db.packages.begin ();
             i != db.packages.end (); ++i)
@@ -175,7 +65,7 @@ PickView::setViewMode (views mode)
                     (pkg.desired &&
                       (pkg.picked () ||               // install bin
                        pkg.srcpicked ())))) // src
-              
+
               // "Up to date" : installed packages that will not be changed
               || (view_mode == PickView::views::PackageKeeps &&
                   (pkg.installed && pkg.desired && !pkg.picked ()
@@ -191,29 +81,13 @@ PickView::setViewMode (views mode)
             {
               // Filter by package name
               if (packageFilterString.empty ()
-                 || StrStrI (pkg.name.c_str (), packageFilterString.c_str ()))
+                  || StrStrI (pkg.name.c_str (), packageFilterString.c_str ()))
                 insert_pkg (pkg);
             }
         }
     }
 
-  RECT r = GetClientRect ();
-  SCROLLINFO si;
-  memset (&si, 0, sizeof (si));
-  si.cbSize = sizeof (si);
-  si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
-  si.nMin = 0;
-  si.nMax = headers[last_col].x + headers[last_col].width;    // + HMARGIN;
-  si.nPage = r.right;
-  SetScrollInfo (GetHWND(), SB_HORZ, &si, TRUE);
-
-  si.nMax = contents.itemcount () * row_height;
-  si.nPage = r.bottom - header_height;
-  SetScrollInfo (GetHWND(), SB_VERT, &si, TRUE);
-
-  scroll_ulc_x = scroll_ulc_y = 0;
-
-  InvalidateRect (GetHWND(), &r, TRUE);
+  listview->setContents(&contents);
 }
 
 PickView::views
@@ -259,7 +133,7 @@ isObsolete (std::set <std::string, casecompare_lt_op> 
&categories)
 bool
 isObsolete (const std::string& catname)
 {
-  if (casecompare(catname, "ZZZRemovedPackages") == 0 
+  if (casecompare(catname, "ZZZRemovedPackages") == 0
         || casecompare(catname, "_", 1) == 0)
     return true;
   return false;
@@ -273,120 +147,84 @@ PickView::setObsolete (bool doit)
   refresh ();
 }
 
-
 void
 PickView::insert_pkg (packagemeta & pkg)
 {
   if (!showObsolete && isObsolete (pkg.categories))
     return;
 
-  PickLine & line = *new PickPackageLine (*this, pkg);
-  contents.insert (line);
+  contents.push_back(new PickPackageLine(*this, pkg));
 }
 
 void
-PickView::insert_category (Category *cat, bool collapsed)
+PickView::insert_category (CategoryTree *cat_tree)
 {
-  // Urk, special case
-  if (casecompare(cat->first, "All") == 0 ||
-      (!showObsolete && isObsolete (cat->first)))
+  if (!cat_tree)
     return;
-  PickCategoryLine & catline = *new PickCategoryLine (*this, *cat, 1, 
collapsed);
-  int packageCount = 0;
-  for (std::vector <packagemeta *>::iterator i = cat->second.begin ();
-       i != cat->second.end () ; ++i)
-    {
-      if (packageFilterString.empty () \
-          || (*i
-             && StrStrI ((*i)->name.c_str (), packageFilterString.c_str ())))
-       {
-         PickLine & line = *new PickPackageLine (*this, **i);
-         catline.insert (line);
-         packageCount++;
-       }
-    }
-  
-  if (packageFilterString.empty () || packageCount)
-    contents.insert (catline);
-  else
-    delete &catline;
-}
-
-int
-PickView::click (int row, int x)
-{
-  return contents.click (0, row, x);
-}
 
+  const Category *cat = &(cat_tree->category());
 
-void
-PickView::scroll (HWND hwnd, int which, int *var, int code, int howmany = 1)
-{
-  SCROLLINFO si;
-  si.cbSize = sizeof (si);
-  si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
-  GetScrollInfo (hwnd, which, &si);
+  // Suppress obsolete category when not showing obsolete
+  if ((!showObsolete && isObsolete (cat->first)))
+    return;
 
-  switch (code)
+  // if it's not the "All" category
+  bool hasContents = false;
+  bool isAll = casecompare(cat->first, "All") == 0;
+  if (!isAll)
     {
-    case SB_THUMBTRACK:
-      si.nPos = si.nTrackPos;
-      break;
-    case SB_THUMBPOSITION:
-      break;
-    case SB_BOTTOM:
-      si.nPos = si.nMax;
-      break;
-    case SB_TOP:
-      si.nPos = 0;
-      break;
-    case SB_LINEDOWN:
-      si.nPos += (row_height * howmany);
-      break;
-    case SB_LINEUP:
-      si.nPos -= (row_height * howmany);
-      break;
-    case SB_PAGEDOWN:
-      si.nPos += si.nPage * 9 / 10;
-      break;
-    case SB_PAGEUP:
-      si.nPos -= si.nPage * 9 / 10;
-      break;
+      // count the number of packages in this category
+      int packageCount = 0;
+      for (std::vector <packagemeta *>::const_iterator i = cat->second.begin 
();
+           i != cat->second.end () ; ++i)
+        {
+          if (packageFilterString.empty ()      \
+              || (*i
+                  && StrStrI ((*i)->name.c_str (), packageFilterString.c_str 
())))
+            {
+              packageCount++;
+            }
+        }
+
+      // if there are some packages in the category, or we are showing 
everything,
+      if (packageFilterString.empty () || packageCount)
+        {
+          hasContents = true;
+        }
     }
 
-  if ((int) si.nPos < 0)
-    si.nPos = 0;
-  if (si.nPos + si.nPage > (unsigned int) si.nMax)
-    si.nPos = si.nMax - si.nPage;
-
-  si.fMask = SIF_POS;
-  SetScrollInfo (hwnd, which, &si, TRUE);
-
-  int ox = scroll_ulc_x;
-  int oy = scroll_ulc_y;
-  *var = si.nPos;
-
-  RECT cr, sr;
-  ::GetClientRect (hwnd, &cr);
-  sr = cr;
-  sr.top += header_height;
-  UpdateWindow (hwnd);
-  ScrollWindow (hwnd, ox - scroll_ulc_x, oy - scroll_ulc_y, &sr, &sr);
-  /*
-     sr.bottom = sr.top;
-     sr.top = cr.top;
-     ScrollWindow (hwnd, ox - scroll_ulc_x, 0, &sr, &sr);
-   */
-  if (ox - scroll_ulc_x)
+  if (!isAll && !hasContents)
+    return;
+
+  // insert line for the category
+  contents.push_back(new PickCategoryLine(*this, cat_tree));
+
+  // if not collapsed
+  if (!cat_tree->collapsed())
     {
-      ::GetClientRect (listheader, &cr);
-      sr = cr;
-//  UpdateWindow (htmp);
-      ::MoveWindow (listheader, -scroll_ulc_x, 0,
-                  headers[last_col].x +
-                  headers[last_col].width, header_height, TRUE);
+      // insert lines for the packages in this category
+      if (!isAll)
+        {
+          for (std::vector <packagemeta *>::const_iterator i = 
cat->second.begin ();
+               i != cat->second.end () ; ++i)
+            {
+              if (packageFilterString.empty ()  \
+                  || (*i
+                      && StrStrI ((*i)->name.c_str (), 
packageFilterString.c_str ())))
+                {
+                  insert_pkg(**i);
+                }
+            }
+        }
+
+      // recurse for contained categories
+      for (std::vector <CategoryTree *>::iterator i = cat_tree->bucket().begin 
();
+           i != cat_tree->bucket().end();
+           i++)
+        {
+          insert_category(*i);
+        }
     }
-  UpdateWindow (hwnd);
 }
 
 /* this means to make the 'category' column wide enough to fit the first 'n'
@@ -394,72 +232,56 @@ PickView::scroll (HWND hwnd, int which, int *var, int 
code, int howmany = 1)
 #define NUM_CATEGORY_COL_WIDTH 2
 
 void
-PickView::init_headers (HDC dc)
+PickView::init_headers (void)
 {
-  int i;
-
-  for (i = 0; headers[i].text; i++)
-    {
-      headers[i].width = 0;
-      headers[i].x = 0;
-    }
-
-  // A margin of 3*GetSystemMetrics(SM_CXEDGE) is used at each side of the
-  // header text.  (Probably should use that rather than hard-coding HMARGIN
-  // everywhere)
-  int addend = 2*3*GetSystemMetrics(SM_CXEDGE);
+  listview->noteColumnWidthStart();
 
-  // accommodate widths of the 'bin' and 'src' checkbox columns
-  note_width (headers, dc, headers[bintick_col].text, addend, bintick_col);
-  note_width (headers, dc, headers[srctick_col].text, addend, srctick_col);
+  // widths of the 'bin' and 'src' checkbox columns just need to accommodate 
the
+  // column name
+  listview->noteColumnWidth (bintick_col, "");
+  listview->noteColumnWidth (srctick_col, "");
 
-  // accomodate the width of each category name
+  // (In category view) accommodate the width of each category name
   packagedb db;
   for (packagedb::categoriesType::iterator n = packagedb::categories.begin();
        n != packagedb::categories.end(); ++n)
     {
-      if (!showObsolete && isObsolete (n->first))
-        continue;
-      note_width (headers, dc, n->first, HMARGIN, cat_col);
+      listview->noteColumnWidth (cat_col, n->first);
     }
 
   /* For each package, accomodate the width of the installed version in the
-     current_col, the widths of all other versions in the new_col, and the
-     width of the sdesc for the pkg_col.  Also, if this is not a Category
-     view, adjust the 'category' column so that the first 
NUM_CATEGORY_COL_WIDTH
-     categories from each package fits.  */
+     current_col, the widths of all other versions in the new_col, and the 
width
+     of the sdesc for the pkg_col and the first NUM_CATEGORY_COL_WIDTH
+     categories in the category column. */
   for (packagedb::packagecollection::iterator n = db.packages.begin ();
        n != db.packages.end (); ++n)
     {
       packagemeta & pkg = *(n->second);
-      if (!showObsolete && isObsolete (pkg.categories))
-        continue;
       if (pkg.installed)
-        note_width (headers, dc, pkg.installed.Canonical_version (),
-                    HMARGIN, current_col);
+        listview->noteColumnWidth (current_col, 
pkg.installed.Canonical_version ());
       for (std::set<packageversion>::iterator i = pkg.versions.begin ();
-          i != pkg.versions.end (); ++i)
-       {
+           i != pkg.versions.end (); ++i)
+        {
           if (*i != pkg.installed)
-            note_width (headers, dc, i->Canonical_version (),
-                        HMARGIN + SPIN_WIDTH, new_col);
-         std::string z = format_1000s(i->source ()->size);
-         note_width (headers, dc, z, HMARGIN, size_col);
-         z = format_1000s(i->sourcePackage ().source ()->size);
-         note_width (headers, dc, z, HMARGIN, size_col);
-       }
+            listview->noteColumnWidth (new_col, i->Canonical_version ());
+          std::string z = format_1000s(i->source ()->size);
+          listview->noteColumnWidth (size_col, z);
+          z = format_1000s(i->sourcePackage ().source ()->size);
+          listview->noteColumnWidth (size_col, z);
+        }
       std::string s = pkg.name;
-      if (pkg.SDesc ().size())
-       s += std::string (": ") + std::string(pkg.SDesc ());
-      note_width (headers, dc, s, HMARGIN, pkg_col);
-      
-      if (view_mode != PickView::views::Category && pkg.categories.size () > 2)
+      listview->noteColumnWidth (pkgname_col, s);
+
+      s = pkg.SDesc ();
+      listview->noteColumnWidth (pkg_col, s);
+
+      if (pkg.categories.size () > 2)
         {
-          std::string compound_cat("");          
+          std::string compound_cat("");
           std::set<std::string, casecompare_lt_op>::const_iterator cat;
           size_t cnt;
-          
-          for (cnt = 0, cat = pkg.categories.begin (); 
+
+          for (cnt = 0, cat = pkg.categories.begin ();
                cnt < NUM_CATEGORY_COL_WIDTH && cat != pkg.categories.end ();
                ++cat)
             {
@@ -470,507 +292,95 @@ PickView::init_headers (HDC dc)
               compound_cat += *cat;
               cnt++;
             }
-          note_width (headers, dc, compound_cat, HMARGIN, cat_col);
+          listview->noteColumnWidth (cat_col, compound_cat);
         }
     }
-  
+
   // ensure that the new_col is wide enough for all the labels
-  const char *captions[] = { "Uninstall", "Skip", "Reinstall", "Retrieve", 
+  const char *captions[] = { "Uninstall", "Skip", "Reinstall", "Retrieve",
                              "Source", "Keep", NULL };
   for (int i = 0; captions[i]; i++)
-    note_width (headers, dc, captions[i], HMARGIN + SPIN_WIDTH, new_col);
-
-  // finally, compute the actual x values based on widths
-  headers[0].x = 0;
-  for (i = 1; i <= last_col; i++)
-    headers[i].x = headers[i - 1].x + headers[i - 1].width;
-  // and allow for resizing to ensure the last column reaches
-  // all the way to the end of the chooser box.
-  headers[last_col].width += total_delta_x;
+    listview->noteColumnWidth (cat_col, captions[i]);
+
+  listview->noteColumnWidthEnd();
+  listview->resizeColumns();
 }
 
-PickView::PickView (Category &cat) : deftrust (TRUST_CURR),
-contents (*this, cat, 0, false, true), showObsolete (false), 
-packageFilterString (), hasWindowRect (false), total_delta_x (0)
+PickView::PickView() :
+  deftrust (TRUST_UNKNOWN),
+  showObsolete (false),
+  packageFilterString (),
+  cat_tree_root (NULL)
 {
 }
 
 void
-PickView::init(views _mode)
+PickView::init(views _mode, ListView *_listview, Window *_parent)
 {
-  HDC dc = GetDC (GetHWND());
-  sysfont = GetStockObject (DEFAULT_GUI_FONT);
-  SelectObject (dc, sysfont);
-  GetTextMetrics (dc, &tm);
-
-  bitmap_dc = CreateCompatibleDC (dc);
-#define LI(x) LoadImage (hinstance, MAKEINTRESOURCE (x), IMAGE_BITMAP, 0, 0, 
0);
-  bm_spin = LI (IDB_SPIN);
-  bm_checkyes = LI (IDB_CHECK_YES);
-  bm_checkno = LI (IDB_CHECK_NO);
-  bm_checkna = LI (IDB_CHECK_NA);
-  bm_treeplus = LI (IDB_TREE_PLUS);
-  bm_treeminus = LI (IDB_TREE_MINUS);  
-#undef LI  
-  icon_dc = CreateCompatibleDC (dc);
-  bm_icon = CreateCompatibleBitmap (dc, 11, 11);
-  SelectObject (icon_dc, bm_icon);
-  rect_icon = CreateRectRgn (0, 0, 11, 11);
-
-  row_height = (tm.tmHeight + tm.tmExternalLeading + ROW_MARGIN);
-  int irh = tm.tmExternalLeading + tm.tmDescent + 11 + ROW_MARGIN;
-  if (row_height < irh)
-    row_height = irh;
-
-  HDLAYOUT hdl;
-  WINDOWPOS wp;
-
-  // Ensure that the common control DLL is loaded, and then create
-  // the header control.
-  INITCOMMONCONTROLSEX controlinfo = { sizeof (INITCOMMONCONTROLSEX), 
-                                       ICC_LISTVIEW_CLASSES };
-  InitCommonControlsEx (&controlinfo);
-
-  if ((listheader = CreateWindowEx (0, WC_HEADER, (LPCTSTR) NULL,
-                                    WS_CHILD | WS_BORDER | CCS_NORESIZE |
-                                    // | HDS_BUTTONS
-                                    HDS_HORZ, 0, 0, 0, 0, GetHWND(),
-                                    (HMENU) IDC_CHOOSE_LISTHEADER, hinstance,
-                                    (LPVOID) NULL)) == NULL)
-    throw new Exception (TOSTRING(__LINE__) " " __FILE__,
-                        "Unable to create list header window",
-                        APPERR_WINDOW_ERROR);
-
-  // Retrieve the bounding rectangle of the parent window's
-  // client area, and then request size and position values
-  // from the header control.
-  RECT rcParent = GetClientRect ();
-
-  hdl.prc = &rcParent;
-  hdl.pwpos = &wp;
-  if (!SendMessage (listheader, HDM_LAYOUT, 0, (LPARAM) & hdl))
-    throw new Exception (TOSTRING(__LINE__) " " __FILE__,
-                        "Unable to get size and position of rectangle",
-                        APPERR_WINDOW_ERROR);
-
-  // Set the font of the listheader, but don't redraw, because its not shown
-  // yet.This message does not return a value, so we are not checking it as we
-  // do above.
-  SendMessage (listheader, WM_SETFONT, (WPARAM) sysfont, FALSE);
-
-  // Set the size, position, and visibility of the header control.
-  SetWindowPos (listheader, wp.hwndInsertAfter, wp.x, wp.y,
-                wp.cx, wp.cy, wp.flags | SWP_SHOWWINDOW);
-
-  header_height = wp.cy;
-  ReleaseDC (GetHWND (), dc);
-
   view_mode = _mode;
-  refresh ();
-}
-
-PickView::~PickView()
-{
-  DeleteDC (bitmap_dc);
-  DeleteObject (bm_spin);
-  DeleteObject (bm_checkyes);
-  DeleteObject (bm_checkno);
-  DeleteObject (bm_checkna);
-  DeleteObject (bm_treeplus);
-  DeleteObject (bm_treeminus);
-  DeleteObject (rect_icon);
-  DeleteObject (bm_icon);
-  DeleteDC (icon_dc);
-}
-
-bool PickView::registerWindowClass ()
-{
-  if (WindowClassAtom != 0)
-    return true;
-
-  // We're not registered yet
-  WNDCLASSEX wc;
-
-  wc.cbSize = sizeof (wc);
-  // Some sensible style defaults
-  wc.style = CS_HREDRAW | CS_VREDRAW;
-  // Our default window procedure.  This replaces itself
-  // on the first call with the simpler Window::WindowProcReflector().
-  wc.lpfnWndProc = Window::FirstWindowProcReflector;
-  // No class bytes
-  wc.cbClsExtra = 0;
-  // One pointer to REFLECTION_INFO in the extra window instance bytes
-  wc.cbWndExtra = 4;
-  // The app instance
-  wc.hInstance = hinstance; //GetInstance ();
-  // Use a bunch of system defaults for the GUI elements
-  wc.hIcon = LoadIcon (0, IDI_APPLICATION);
-  wc.hIconSm = NULL;
-  wc.hCursor = LoadCursor (0, IDC_ARROW);
-  wc.hbrBackground = NULL;
-  // No menu
-  wc.lpszMenuName = NULL;
-  // We'll get a little crazy here with the class name
-  wc.lpszClassName = "listview";
-
-  // All set, try to register
-  WindowClassAtom = RegisterClassEx (&wc);
-  if (WindowClassAtom == 0)
-    Log (LOG_BABBLE) << "Failed to register listview " << GetLastError () << 
endLog;
-  return WindowClassAtom != 0;
-}
-
-LRESULT CALLBACK
-PickView::list_vscroll (HWND hwnd, HWND hctl, UINT code, int pos)
-{
-  scroll (hwnd, SB_VERT, &scroll_ulc_y, code);
-  return 0;
-}
-
-LRESULT CALLBACK
-PickView::list_hscroll (HWND hwnd, HWND hctl, UINT code, int pos)
-{
-  scroll (hwnd, SB_HORZ, &scroll_ulc_x, code);
-  return 0;
+  listview = _listview;
+  parent = _parent;
 }
 
 void
-PickView::set_vscroll_info (const RECT &r)
+PickView::build_category_tree()
 {
-  SCROLLINFO si;
-  memset (&si, 0, sizeof (si));
-  si.cbSize = sizeof (si);
-  si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;    /* SIF_RANGE was giving strange 
behaviour */
-  si.nMin = 0;
-
-  si.nMax = contents.itemcount () * row_height;
-  si.nPage = r.bottom - header_height;
-
-  /* if we are under the minimum display count ,
-   * set the offset to 0
-   */
-  if ((unsigned int) si.nMax <= si.nPage)
-    scroll_ulc_y = 0;
-  si.nPos = scroll_ulc_y;
-
-  SetScrollInfo (GetHWND(), SB_VERT, &si, TRUE);
-}
+  /* Build the category tree */
 
-LRESULT CALLBACK
-PickView::list_click (HWND hwnd, BOOL dblclk, int x, int y, UINT hitCode)
-{
-  int row, refresh __attribute__ ((unused));
-
-  if (contents.itemcount () == 0)
-    return 0;
-
-  if (y < header_height)
-    return 0;
-  x += scroll_ulc_x;
-  y += scroll_ulc_y - header_height;
-
-  row = (y + ROW_MARGIN / 2) / row_height;
+  /* Start collapsed. TODO: make that a flag */
+  bool collapsed = true;
 
-  if (row < 0 || row >= contents.itemcount ())
-    return 0;
-
-  refresh = click (row, x);
-
-  // XXX we need a method to query the database to see if more
-  // than just one package has changed! Until then...
-#if 0
-  if (refresh)
+  /* Dispose of any existing category tree */
+  if (cat_tree_root)
     {
-#endif
-      RECT r = GetClientRect ();
-      set_vscroll_info (r);
-      InvalidateRect (GetHWND(), &r, TRUE);
-#if 0
-    }
-  else
-    {
-      RECT rect;
-      rect.left =
-        headers[new_col].x - scroll_ulc_x;
-      rect.right =
-        headers[src_col + 1].x - scroll_ulc_x;
-      rect.top =
-        header_height + row * row_height -
-        scroll_ulc_y;
-      rect.bottom = rect.top + row_height;
-      InvalidateRect (hwnd, &rect, TRUE);
+      for (std::vector <CategoryTree *>::const_iterator i = 
cat_tree_root->bucket().begin();
+           i != cat_tree_root->bucket().end();
+           i++)
+        delete *i;
+
+      delete cat_tree_root;
+      cat_tree_root = NULL;
     }
-#endif
-  return 0;
-}
 
-/*
- * LRESULT CALLBACK
- * PickView::listview_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM 
lParam)
- */
-LRESULT
-PickView::WindowProc (UINT message, WPARAM wParam, LPARAM lParam)
-{
-  int wheel_notches;
-  UINT wheel_lines;
-  
-  switch (message)
+  /* Find the 'All' category */
+  for (packagedb::categoriesType::iterator n =
+         packagedb::categories.begin(); n != packagedb::categories.end();
+       ++n)
     {
-    case WM_HSCROLL:
-      list_hscroll (GetHWND(), (HWND)lParam, LOWORD(wParam), HIWORD(wParam));
-      return 0;
-    case WM_VSCROLL:
-      list_vscroll (GetHWND(), (HWND)lParam, LOWORD(wParam), HIWORD(wParam));
-      return 0;
-    case WM_MOUSEWHEEL:
-      // this is how many 'notches' the wheel scrolled, forward/up = positive
-      wheel_notches = GET_WHEEL_DELTA_WPARAM(wParam) / 120;
-      
-      // determine how many lines the user has configred for a mouse scroll
-      SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_lines, 0);
-
-      if (wheel_lines == 0)   // do no scrolling
-        return 0;
-      else if (wheel_lines == WHEEL_PAGESCROLL)
-        scroll (GetHWND (), SB_VERT, &scroll_ulc_y, (wheel_notches > 0) ?
-                SB_PAGEUP : SB_PAGEDOWN);
-      else
-        scroll (GetHWND (), SB_VERT, &scroll_ulc_y, (wheel_notches > 0) ?
-                SB_LINEUP : SB_LINEDOWN, wheel_lines * abs (wheel_notches));
-      return 0; // handled
-    case WM_LBUTTONDOWN:
-      list_click (GetHWND(), FALSE, LOWORD(lParam), HIWORD(lParam), wParam);
-      return 0;
-    case WM_PAINT:
-      paint (GetHWND());
-      return 0;
-    case WM_NOTIFY:
-      {
-        // pnmh = (LPNMHDR) lParam
-        LPNMHEADER phdr = (LPNMHEADER) lParam;
-        switch (phdr->hdr.code)
-          {
-          case HDN_ITEMCHANGED:
-            if (phdr->hdr.hwndFrom == ListHeader ())
-              {
-                if (phdr->pitem && phdr->pitem->mask & HDI_WIDTH)
-                  headers[phdr->iItem].width = phdr->pitem->cxy;
-  
-                for (int i = 1; i <= last_col; i++)
-                  headers[i].x = headers[i - 1].x + headers[i - 1].width;
-  
-                RECT r = GetClientRect ();
-                SCROLLINFO si;
-                si.cbSize = sizeof (si);
-                si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
-                GetScrollInfo (GetHWND(), SB_HORZ, &si);
-  
-                int oldMax = si.nMax;
-                si.nMax = headers[last_col].x + headers[last_col].width;
-                if (si.nTrackPos && oldMax > si.nMax)
-                  si.nTrackPos += si.nMax - oldMax;
-  
-                si.nPage = r.right;
-                SetScrollInfo (GetHWND(), SB_HORZ, &si, TRUE);
-                InvalidateRect (GetHWND(), &r, TRUE);
-                if (si.nTrackPos && oldMax > si.nMax)
-                  scroll (GetHWND(), SB_HORZ, &scroll_ulc_x, SB_THUMBTRACK);
-              }
-            break;
-          }
+      if (casecompare(n->first, "All") == 0)
+        {
+          cat_tree_root = new CategoryTree(*n, collapsed);
+          break;
         }
-      break;
-    case WM_SIZE:
-      {
-        // Note: WM_SIZE msgs only appear when 'just' scrolling the window
-        RECT windowRect = GetWindowRect ();
-        if (hasWindowRect)
-          {
-            int dx;
-            if ((dx = windowRect.right - windowRect.left -
-                        lastWindowRect.width ()) != 0)
-              {
-                cat_headers[set_header_column_order (views::Category)].width 
+= dx;
-                pkg_headers[set_header_column_order 
(views::PackagePending)].width += dx;
-                set_header_column_order (view_mode);
-                set_headers ();
-                ::MoveWindow (listheader, -scroll_ulc_x, 0,
-                            headers[last_col].x +
-                            headers[last_col].width, header_height, TRUE);
-                total_delta_x += dx;
-              }
-           if (windowRect.bottom - windowRect.top - lastWindowRect.height ())
-             set_vscroll_info (GetClientRect ());
-          }
-        else
-          hasWindowRect = true;
-  
-        lastWindowRect = windowRect;
-        return 0;     
-      }
-    case WM_MOUSEACTIVATE:
-      SetFocus(GetHWND());
-      return MA_ACTIVATE;
     }
-  
-  // default: can't handle this message
-  return DefWindowProc (GetHWND(), message, wParam, lParam);
-}
 
-////
-// Turn black into foreground color and white into background color by
-//   1) Filling a square with ~(FG^BG)
-//   2) Blitting the bitmap on it with NOTSRCERASE (white->black; black->FG^BG)
-//   3) Blitting the result on BG with SRCINVERT (white->BG; black->FG)
-void
-PickView::DrawIcon (HDC hdc, int x, int y, HANDLE hIcon)
-{
-  SelectObject (bitmap_dc, hIcon);
-  FillRgn (icon_dc, rect_icon, bg_fg_brush);
-  BitBlt (icon_dc, 0, 0, 11, 11, bitmap_dc, 0, 0, NOTSRCERASE);
-  BitBlt (hdc, x, y, 11, 11, icon_dc, 0, 0, SRCINVERT);
-///////////// On WinNT-based systems, we could've done the below instead
-///////////// See http://support.microsoft.com/default.aspx?scid=kb;en-us;79212
-//      SelectObject (hdc, GetSysColorBrush (COLOR_WINDOWTEXT));
-//      HBITMAP bm_icon = CreateBitmap (11, 11, 1, 1, NULL);
-//      SelectObject (icon_dc, bm_icon);
-//      BitBlt (icon_dc, 0, 0, 11, 11, bitmap_dc, 0, 0, SRCCOPY);
-//      MaskBlt (hdc, x2, by, 11, 11, bitmap_dc, 0, 0, bm_icon, 0, 0, MAKEROP4 
(SRCAND, PATCOPY));
-//      DeleteObject (bm_icon);
-}
-
-void
-PickView::paint (HWND hwnd)
-{
-  // we want to retrieve the update region before calling BeginPaint,
-  // because after we do that the update region is validated and we can
-  // no longer retrieve it
-  HRGN hUpdRgn = CreateRectRgn (0, 0, 0, 0);
-
-  if (GetUpdateRgn (hwnd, hUpdRgn, FALSE) == 0)
+  /* Add all the other categories as children */
+  for (packagedb::categoriesType::iterator n =
+         packagedb::categories.begin(); n != packagedb::categories.end();
+       ++n)
     {
-      // error?
-      return;
-    }
-
-  // tell the system that we're going to begin painting our window
-  // it will prevent further WM_PAINT messages from arriving until we're
-  // done, and if any part of our window was invalidated while we are
-  // painting, it will retrigger us so that we can fix it
-  PAINTSTRUCT ps;
-  HDC hdc = BeginPaint (hwnd, &ps);
- 
-  SelectObject (hdc, sysfont);
-  SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT));
-  SetBkColor (hdc, GetSysColor (COLOR_WINDOW));
-  FillRgn (hdc, hUpdRgn, GetSysColorBrush(COLOR_WINDOW));
-
-  COLORREF clr = ~GetSysColor (COLOR_WINDOW) ^ GetSysColor (COLOR_WINDOWTEXT);
-  clr = RGB (GetRValue (clr), GetGValue (clr), GetBValue (clr)); // reconvert
-  bg_fg_brush = CreateSolidBrush (clr);
-
-  RECT cr;
-  ::GetClientRect (hwnd, &cr);
-
-  int x = cr.left - scroll_ulc_x;
-  int y = cr.top - scroll_ulc_y + header_height;
-
-  contents.paint (hdc, hUpdRgn, x, y, 0, (view_mode == 
-                                  PickView::views::Category) ? 0 : 1);
+      if (casecompare(n->first, "All") == 0)
+        continue;
 
-  if (contents.itemcount () == 0)
-    {
-      static const char *msg = "Nothing to Install/Update";
-      if (source == IDC_SOURCE_DOWNLOAD)
-        msg = "Nothing to Download";
-      TextOut (hdc, x + HMARGIN, y, msg, strlen (msg));
+      CategoryTree *cat_tree = new CategoryTree(*n, collapsed);
+      cat_tree_root->bucket().push_back(cat_tree);
     }
 
-  DeleteObject (hUpdRgn);
-  DeleteObject (bg_fg_brush);
-  EndPaint (hwnd, &ps);
+  refresh ();
 }
 
-
-bool 
-PickView::Create (Window * parent, DWORD Style, RECT *r)
+PickView::~PickView()
 {
-
-  // First register the window class, if we haven't already
-  if (!registerWindowClass ())
-    {
-      // Registration failed
-      return false;
-    }
-
-  // Save our parent, we'll probably need it eventually.
-  setParent(parent);
-
-  // Create the window instance
-  CreateWindowEx (// Extended Style
-                  WS_EX_CLIENTEDGE,
-                  // window class atom (name)
-                  "listview",   //MAKEINTATOM(WindowClassAtom),
-                  "listviewwindow", // no title-bar string yet
-                  // Style bits
-                  Style,
-                  r ? r->left : CW_USEDEFAULT,
-                  r ? r->top : CW_USEDEFAULT,
-                  r ? r->right - r->left + 1 : CW_USEDEFAULT,
-                  r ? r->bottom - r->top + 1 : CW_USEDEFAULT,
-                  // Parent Window
-                  parent == NULL ? (HWND)NULL : parent->GetHWND (),
-                  // use class menu
-                  (HMENU) MAKEINTRESOURCE (IDC_CHOOSE_LIST),
-                  // The application instance
-                  GetInstance (),
-                  // The this ptr, which we'll use to set up
-                  // the WindowProc reflection.
-                  reinterpret_cast<void *>((Window *)this));
-  if (GetHWND() == NULL)
-    {
-      Log (LOG_BABBLE) << "Failed to create PickView " << GetLastError () << 
endLog;
-      return false;
-    }
-
-  return true;
 }
 
 void
 PickView::defaultTrust (trusts trust)
 {
   this->deftrust = trust;
-
-  // force the picker to redraw
-  RECT r = GetClientRect ();
-  InvalidateRect (this->GetHWND(), &r, TRUE);
 }
 
-/* This recalculates all column widths and resets the view */
 void
 PickView::refresh()
 {
-  HDC dc = GetDC (GetHWND ());
-  
-  // we must set the font of the DC here, otherwise the width calculations
-  // will be off because the system will use the wrong font metrics
-  sysfont = GetStockObject (DEFAULT_GUI_FONT);
-  SelectObject (dc, sysfont);
-
-  // init headers for the current mode
-  set_headers ();
-  init_headers (dc);
-  
-  // save the current mode
-  views cur_view_mode = view_mode;
-  
-  // switch to the other type and do those headers
-  view_mode = (view_mode == PickView::views::Category) ? 
-                    PickView::views::PackageFull : PickView::views::Category;
-  set_headers ();
-  init_headers (dc);
-  ReleaseDC (GetHWND (), dc);
-
-  view_mode = cur_view_mode;
   setViewMode (view_mode);
 }
diff --git a/PickView.h b/PickView.h
index 298f844..9777d15 100644
--- a/PickView.h
+++ b/PickView.h
@@ -17,84 +17,17 @@
 #define SETUP_PICKVIEW_H
 
 #include <string>
-#include "win32.h"
-#include "window.h"
-#include "RECTWrapper.h"
-
-#define HMARGIN         10
-#define ROW_MARGIN      5
-#define ICON_MARGIN     4
-#define SPIN_WIDTH      11
-#define CHECK_SIZE      11
-#define TREE_INDENT     12
-
-#define CATEGORY_EXPANDED  0
-#define CATEGORY_COLLAPSED 1
-
-class PickView;
-#include "PickCategoryLine.h"
+
 #include "package_meta.h"
+#include "ListView.h"
+
+class Window;
+class CategoryTree;
 
-class PickView : public Window
+class PickView
 {
 public:
-  virtual bool Create (Window * Parent = NULL, DWORD Style = 
WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, RECT * r = NULL);
-  virtual bool registerWindowClass ();
-  enum class views;
-  class Header;
-  int num_columns;
-  void defaultTrust (trusts trust);
-  void setViewMode (views mode);
-  views getViewMode ();
-  void DrawIcon (HDC hdc, int x, int y, HANDLE hIcon);
-  void paint (HWND hwnd);
-  LRESULT CALLBACK list_click (HWND hwnd, BOOL dblclk, int x, int y, UINT 
hitCode);
-  LRESULT CALLBACK list_hscroll (HWND hwnd, HWND hctl, UINT code, int pos);
-  LRESULT CALLBACK list_vscroll (HWND hwnd, HWND hctl, UINT code, int pos);
-  void set_vscroll_info (const RECT &r);
-  virtual LRESULT WindowProc (UINT uMsg, WPARAM wParam, LPARAM lParam);
-  Header *headers;
-  PickView (Category & cat);
-  void init(views _mode);
-  ~PickView();
-  static const char *mode_caption (views mode);
-  void setObsolete (bool doit);
-  void insert_pkg (packagemeta &);
-  void insert_category (Category *, bool);
-  int click (int row, int x);
-  void refresh();
-  int current_col;
-  int new_col;
-  int bintick_col;
-  int srctick_col;
-  int cat_col;
-  int size_col;
-  int pkg_col;
-  int last_col;
-  int row_height;
-  TEXTMETRIC tm;
-  HDC bitmap_dc, icon_dc;
-  HBITMAP bm_icon;
-  HRGN rect_icon;
-  HBRUSH bg_fg_brush;
-  HANDLE bm_spin, bm_checkyes, bm_checkno, bm_checkna, bm_treeplus, 
bm_treeminus;
-  trusts deftrust;
-  HANDLE sysfont;
-  int scroll_ulc_x, scroll_ulc_y;
-  int header_height;
-  PickCategoryLine contents;
-  void scroll (HWND hwnd, int which, int *var, int code, int howmany);
-
-  void SetPackageFilter (const std::string &filterString)
-  {
-    packageFilterString = filterString;
-  }
-  
-  
-  HWND ListHeader (void) const
-  {
-    return listheader;
-  }
+  trusts deftrust; // XXX: needs accessor
 
   enum class views
   {
@@ -106,35 +39,135 @@ public:
     Category,
   };
 
-  class Header
+  PickView ();
+  ~PickView();
+  void defaultTrust (trusts trust);
+  void setViewMode (views mode);
+  views getViewMode ();
+  void init(views _mode, ListView *_listview, Window *parent);
+  void build_category_tree();
+  static const char *mode_caption (views mode);
+  void setObsolete (bool doit);
+  void refresh();
+  void init_headers ();
+
+  void SetPackageFilter (const std::string &filterString)
   {
-  public:
-    const char *text;
-    int width;
-    int x;
-    bool needs_clip;
-  };
+    packageFilterString = filterString;
+  }
+
+  Window *GetParent(void) { return parent; }
 
 private:
-  static ATOM WindowClassAtom;
-  HWND listheader;
   views view_mode;
+  ListView *listview;
   bool showObsolete;
   std::string packageFilterString;
+  ListViewContents contents;
+  CategoryTree *cat_tree_root;
+  Window *parent;
 
-  // Stuff needed to handle resizing
-  bool hasWindowRect;
-  RECTWrapper lastWindowRect;
-  int total_delta_x;
+  void insert_pkg (packagemeta &);
+  void insert_category (CategoryTree *);
+};
 
-  int set_header_column_order (views vm);
-  void set_headers ();
-  void init_headers (HDC dc);
-  void note_width (Header *hdrs, HDC dc, const std::string& string,
-                   int addend, int column);
+enum
+{
+ pkgname_col = 0, // package/category name
+ current_col = 1,
+ new_col = 2,     // action
+ bintick_col = 3,
+ srctick_col = 4,
+ cat_col = 5,
+ size_col = 6,
+ pkg_col = 7,     // desc
 };
 
 bool isObsolete (std::set <std::string, casecompare_lt_op> &categories);
 bool isObsolete (const std::string& catname);
 
+//
+// Helper class which stores the contents and collapsed/expanded state for each
+// category (and the pseudo-category 'All')
+//
+
+class CategoryTree
+{
+public:
+  CategoryTree(Category & cat, bool collapsed) :
+    _cat (cat),
+    _collapsed(collapsed),
+    _action (packagemeta::Default_action)
+  {
+  }
+
+  ~CategoryTree()
+  {
+  }
+
+  std::vector <CategoryTree *> & bucket()
+  {
+    return _bucket;
+  }
+
+  bool &collapsed()
+  {
+    return _collapsed;
+  }
+
+  const Category &category()
+  {
+    return _cat;
+  }
+
+  packagemeta::_actions & action()
+  {
+    return _action;
+  }
+
+  int do_action(packagemeta::_actions action_id, trusts const deftrust)
+  {
+    int u = 1;
+    _action = action_id;
+
+    if (_bucket.size())
+      {
+        for (std::vector <CategoryTree *>::const_iterator i = _bucket.begin();
+             i != _bucket.end();
+             i++)
+          {
+            // recurse for all contained categories
+            int l = (*i)->do_action(action_id, deftrust);
+
+            if (!_collapsed)
+              u += l;
+          }
+      }
+    else
+      {
+        // otherwise, this is a leaf category, so apply action to all packages
+        // in this category
+        int l = 0;
+        for (std::vector <packagemeta *>::const_iterator pkg = 
_cat.second.begin();
+             pkg != _cat.second.end();
+             ++pkg)
+          {
+            (*pkg)->set_action(action_id, (*pkg)->trustp(true, deftrust));
+            l++;
+          }
+
+        // these lines need to be updated, if displayed
+        if (!_collapsed)
+          u += l;
+      }
+    return u;
+  }
+
+private:
+  Category & _cat;
+  bool _collapsed;
+  std::vector <CategoryTree *> _bucket;
+  packagemeta::_actions _action;
+};
+
 #endif /* SETUP_PICKVIEW_H */
diff --git a/check-na.bmp b/check-na.bmp
deleted file mode 100644
index 
c139e54d514a5b00997d9888d47f9f6d1fdd410b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 106
zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q8U8<DVE7Kj*$xa0Sq(sZ0D`3z7#RLO
IfMAe10G1vT>i_@%

diff --git a/check-no.bmp b/check-no.bmp
deleted file mode 100644
index 
3639605be72d99fb7d2947b24832629609e8007d..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 106
qcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q!GiS%7#Ij)kU{`6UJuv+

diff --git a/check-yes.bmp b/check-yes.bmp
deleted file mode 100644
index 
f328dc2fe2899e6a350fb743b3820941ab1c68db..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 106
zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q!GiS%7#QLm7#Q+^bSV&5Lun8nBo2}X
F0RRy04qgBN

diff --git a/choose-spin.bmp b/choose-spin.bmp
deleted file mode 100644
index 
8779f6dc20ebfb35c22b7b97afda8fcad8d93664..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 106
zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q89qK>V0Z___Y)Wx*nyZ$fq|h9h<g|q
O814WuNE{>&QU?G_z7YTb

diff --git a/choose.cc b/choose.cc
index 51d2fb6..c86294a 100644
--- a/choose.cc
+++ b/choose.cc
@@ -81,7 +81,6 @@ static ControlAdjuster::ControlInfo ChooserControlsInfo[] = {
   {IDC_CHOOSE_SYNC,            CP_RIGHT,   CP_TOP},
   {IDC_CHOOSE_EXP,             CP_RIGHT,   CP_TOP},
   {IDC_CHOOSE_VIEW,            CP_LEFT,    CP_TOP},
-  {IDC_LISTVIEW_POS,           CP_RIGHT,   CP_TOP},
   {IDC_CHOOSE_VIEWCAPTION,     CP_LEFT,    CP_TOP},
   {IDC_CHOOSE_LIST,            CP_STRETCH, CP_STRETCH},
   {IDC_CHOOSE_HIDE,             CP_LEFT,    CP_BOTTOM},
@@ -132,23 +131,33 @@ ChooserPage::~ChooserPage ()
     }
 }
 
+static ListView::Header pkg_headers[] = {
+  {"Package",     LVCFMT_LEFT},
+  {"Current",     LVCFMT_LEFT},
+  {"New",         LVCFMT_LEFT},
+  {"Bin?",        LVCFMT_LEFT},
+  {"Src?",        LVCFMT_LEFT},
+  {"Categories",  LVCFMT_LEFT},
+  {"Size",        LVCFMT_RIGHT},
+  {"Description", LVCFMT_LEFT},
+  {0}
+};
+
 void
 ChooserPage::createListview ()
 {
   SetBusy ();
-  static std::vector<packagemeta *> empty_cat;
-  static Category dummy_cat (std::string ("All"), empty_cat);
-  chooser = new PickView (dummy_cat);
-  RECT r = getDefaultListViewSize();
-  if (!chooser->Create(this, WS_CHILD | WS_HSCROLL | WS_VSCROLL | 
WS_VISIBLE,&r))
-    throw new Exception (TOSTRING(__LINE__) " " __FILE__,
-                        "Unable to create chooser list window",
-                        APPERR_WINDOW_ERROR);
-  chooser->init(PickView::views::Category);
-  chooser->Show(SW_SHOW);
+
+  listview = new ListView();
+  listview->init(GetHWND (), IDC_CHOOSE_LIST, pkg_headers);
+
+  chooser = new PickView();
+  chooser->init(PickView::views::Category, listview, this);
   chooser->setViewMode (!is_new_install || UpgradeAlsoOption || 
hasManualSelections ?
-                       PickView::views::PackagePending : 
PickView::views::Category);
+                        PickView::views::PackagePending : 
PickView::views::Category);
+
   SendMessage (GetDlgItem (IDC_CHOOSE_VIEW), CB_SETCURSEL, 
(WPARAM)chooser->getViewMode(), 0);
+
   ClearBusy ();
 }
 
@@ -240,16 +249,6 @@ ChooserPage::setPrompt(char const *aString)
   ::SetWindowText (GetDlgItem (IDC_CHOOSE_INST_TEXT), aString);
 }
 
-RECT
-ChooserPage::getDefaultListViewSize()
-{
-  RECT result;
-  getParentRect (GetHWND (), GetDlgItem (IDC_LISTVIEW_POS), &result);
-  result.top += 2;
-  result.bottom -= 2;
-  return result;
-}
-
 void
 ChooserPage::OnInit ()
 {
@@ -336,6 +335,17 @@ ChooserPage::OnActivate()
       activated = true;
     }
 
+  packagedb::categoriesType::iterator it = db.categories.find("All");
+  if (it == db.categories.end ())
+    listview->setEmptyText("No packages found.");
+  if (source == IDC_SOURCE_DOWNLOAD)
+    listview->setEmptyText("Nothing to download.");
+  else
+    listview->setEmptyText("Nothing to install or update.");
+
+  chooser->build_category_tree();
+  chooser->init_headers();
+
   ClearBusy();
 
   chooser->refresh();
@@ -447,7 +457,7 @@ bool
 ChooserPage::OnMessageCmd (int id, HWND hwndctl, UINT code)
 {
 #if DEBUG
-  Log (LOG_BABBLE) << "OnMessageCmd " << id << " " << hwndctl << " " << 
std::hex << code << endLog;
+  Log (LOG_BABBLE) << "ChooserPage::OnMessageCmd " << id << " " << hwndctl << 
" " << std::hex << code << endLog;
 #endif
 
   if (id == IDC_CHOOSE_SEARCH_EDIT)
@@ -549,10 +559,19 @@ ChooserPage::OnMessageCmd (int id, HWND hwndctl, UINT 
code)
   return false;
 }
 
-INT_PTR CALLBACK
-ChooserPage::OnMouseWheel (UINT message, WPARAM wParam, LPARAM lParam)
+bool
+ChooserPage::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
 {
-  return chooser->WindowProc (message, wParam, lParam);
+#if DEBUG
+  Log (LOG_BABBLE) << "ChooserPage::OnNotify id:" << pNmHdr->idFrom << " 
hwnd:" << pNmHdr->hwndFrom << " code:" << (int)pNmHdr->code << endLog;
+#endif
+
+  // offer messages to the listview
+  if (listview->OnNotify(pNmHdr, pResult))
+    return true;
+
+  // we don't care
+  return false;
 }
 
 INT_PTR CALLBACK
diff --git a/choose.h b/choose.h
index 32a1650..e4953b7 100644
--- a/choose.h
+++ b/choose.h
@@ -20,6 +20,7 @@
 #include "proppage.h"
 #include "package_meta.h"
 #include "PickView.h"
+#include "ListView.h"
 
 #define DEFAULT_TIMER_ID   5   //value doesn't matter, as long as it's unique
 #define SEARCH_TIMER_DELAY 500 //in milliseconds
@@ -33,8 +34,7 @@ public:
   ~ChooserPage ();
 
   virtual bool OnMessageCmd (int id, HWND hwndctl, UINT code);
-  virtual INT_PTR CALLBACK OnMouseWheel (UINT message, WPARAM wParam,
-                                        LPARAM lParam);
+  virtual bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult);
   virtual INT_PTR CALLBACK OnTimerMessage (UINT message, WPARAM wParam,
                                                                                
   LPARAM lparam);
 
@@ -51,7 +51,6 @@ public:
   }
 private:
   void createListview ();
-  RECT getDefaultListViewSize();
   void getParentRect (HWND parent, HWND child, RECT * r);
   void keepClicked();
   void changeTrust(int button, bool test, bool initial);
@@ -63,6 +62,7 @@ private:
   void initialUpdateState();
 
   PickView *chooser;
+  ListView *listview;
   static HWND ins_dialog;
   bool cmd_show_set;
   bool saved_geom;
diff --git a/main.cc b/main.cc
index 1374fb6..8ceaa4d 100644
--- a/main.cc
+++ b/main.cc
@@ -145,7 +145,7 @@ main_display ()
 
   // Initialize common controls
   INITCOMMONCONTROLSEX icce = { sizeof (INITCOMMONCONTROLSEX),
-                               ICC_WIN95_CLASSES };
+                               ICC_WIN95_CLASSES | ICC_LISTVIEW_CLASSES };
   InitCommonControlsEx (&icce);
 
   // Initialize COM and ShellLink instance here.  For some reason
diff --git a/res.rc b/res.rc
index 02b60cf..27f0378 100644
--- a/res.rc
+++ b/res.rc
@@ -1,5 +1,6 @@
 #include "resource.h"
 #include "windows.h"
+#include "commctrl.h"
 
 #define SETUP_STANDARD_DIALOG_W        339
 #define SETUP_STANDARD_DIALOG_H        179
@@ -357,8 +358,8 @@ BEGIN
                     SETUP_EXP_X, 30, SETUP_KPCE_W, 14
     CONTROL         "", IDC_HEADSEPARATOR, "Static", SS_BLACKFRAME | SS_SUNKEN,
                     0, 28, SETUP_STANDARD_DIALOG_W, 1
-    CONTROL         "", IDC_LISTVIEW_POS, "Static", SS_BLACKFRAME | NOT 
-                    WS_VISIBLE, 7, 45, SETUP_STANDARD_DIALOG_W - 14, 122
+    CONTROL         "", IDC_CHOOSE_LIST, WC_LISTVIEW, LVS_NOSORTHEADER | 
LVS_REPORT | LVS_SINGLESEL,
+                    7, 47, SETUP_STANDARD_DIALOG_W - 14, 120, WS_EX_CLIENTEDGE
     CONTROL         "&Hide obsolete packages", IDC_CHOOSE_HIDE,
                     "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 7, 167, 160, 14
     ICON            IDI_CYGWIN, IDC_HEADICON, SETUP_HEADICON_X, 0, 21, 20
@@ -528,18 +529,6 @@ CYGWIN-SETUP.ICON       FILE    DISCARDABLE     
"cygwin-setup.ico"
 CYGWIN.ICON             FILE    DISCARDABLE     "cygwin.ico"
 CYGWIN-TERMINAL.ICON    FILE    DISCARDABLE     "cygwin-terminal.ico"
 
-/////////////////////////////////////////////////////////////////////////////
-//
-// Bitmap
-//
-
-IDB_SPIN                BITMAP  DISCARDABLE     "choose-spin.bmp"
-IDB_CHECK_YES           BITMAP  DISCARDABLE     "check-yes.bmp"
-IDB_CHECK_NO            BITMAP  DISCARDABLE     "check-no.bmp"
-IDB_CHECK_NA            BITMAP  DISCARDABLE     "check-na.bmp"
-IDB_TREE_PLUS           BITMAP  DISCARDABLE     "tree-plus.bmp"
-IDB_TREE_MINUS          BITMAP  DISCARDABLE     "tree-minus.bmp"
-
 /////////////////////////////////////////////////////////////////////////////
 //
 // String Table
diff --git a/resource.h b/resource.h
index 421a24c..2f1036b 100644
--- a/resource.h
+++ b/resource.h
@@ -71,15 +71,6 @@
 #define IDD_DOWNLOAD_ERROR                224
 #define IDD_CONFIRM                       225
 
-// Bitmaps
-
-#define IDB_SPIN                          300
-#define IDB_CHECK_YES                     301
-#define IDB_CHECK_NO                      302
-#define IDB_CHECK_NA                      303
-#define IDB_TREE_PLUS                     304
-#define IDB_TREE_MINUS                    305
-
 // icons
 
 #define IDI_CYGWIN_SETUP                  401
@@ -118,7 +109,6 @@
 #define IDC_NET_USER                      527
 #define IDC_NET_PASSWD                    528
 #define IDC_VERSION                       529
-#define IDC_LISTVIEW_POS                  530
 #define IDC_CHOOSE_VIEW                   531
 #define IDC_CHOOSE_EXP                    532
 #define IDC_CHOOSE_BEST                   533
@@ -135,7 +125,6 @@
 #define IDC_DLS_IPROGRESS_TEXT            545
 #define IDC_CHOOSE_INST_TEXT              546
 #define IDC_CHOOSE_VIEWCAPTION            547
-#define IDC_CHOOSE_LISTHEADER             548
 #define IDC_INS_BL_PACKAGE                549
 #define IDC_INS_BL_TOTAL                  550
 #define IDC_INS_BL_DISK                   551
diff --git a/tree-minus.bmp b/tree-minus.bmp
deleted file mode 100644
index 
35b2221b89542e9c1b37b10969107f3fc0f72362..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 106
zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q8U8<DU}#WaVA#I^ffpdLLE<2JkU9W?
C))N^3

diff --git a/tree-plus.bmp b/tree-plus.bmp
deleted file mode 100644
index 
e0335d970ffda606e31f76a84d1c56298106e4bb..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 106
zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q8U8<DU}#WaVA#KafnhfkFF<00#6j{P
FbpU`?6A1tS

-- 
2.17.0

Reply via email to