public inbox for cygwin-apps-cvs@sourceware.org
help / color / mirror / Atom feed
* [setup - the official Cygwin setup program] branch master, updated. release_2.894
@ 2018-10-13 18:13 jturney
  0 siblings, 0 replies; only message in thread
From: jturney @ 2018-10-13 18:13 UTC (permalink / raw)
  To: cygwin-apps-cvs




https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=7a524bf7194058086410dc442a7cc1405c6f92a0

commit 7a524bf7194058086410dc442a7cc1405c6f92a0
Merge: f318f27 dff279e
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sat Oct 13 18:56:11 2018 +0100

    Merge branch 'topic/listview'

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=dff279e20df996197676a2f1709e7039ee16b5f9

commit dff279e20df996197676a2f1709e7039ee16b5f9
Author: Ken Brown <kbrown@cornell.edu>
Date:   Mon Aug 6 10:05:12 2018 -0400

    Ensure that an installed packageversion has an ldesc if possible
    
    In packagedb::read(), copy the ldesc from setup.ini to the
    packageversion read from installed.db.  Otherwise, an installed
    package with only one version will not have an ldesc to use as a
    tooltip.
    
    v2: If the installed version is no longer available, copy the ldesc
    from the current version of the package.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=800274fd5e88c6d48f6315d05bbbd1501bb8cefd

commit 800274fd5e88c6d48f6315d05bbbd1501bb8cefd
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Fri Jul 20 12:52:20 2018 +0100

    Add ldesc tooltips to sdesc column of listview
    
    Listview's built-in tooltip support doesn't support subitems, so we have to
    build our own
    
    v2: Use string cache

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=e8f2c078b53dc4d56a8903a687deac3be18406b5

commit e8f2c078b53dc4d56a8903a687deac3be18406b5
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed Jul 18 20:50:31 2018 +0100

    Restore packagemeta::LDesc()
    
    v2:
    Update for removed 'usiing namespace std'

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=30cae42fa9bfce260b88550cbb568d578c958e61

commit 30cae42fa9bfce260b88550cbb568d578c958e61
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sun Jul 15 15:41:12 2018 +0100

    Add LDesc() accessor method to SolvableVersion

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=68487c7ca5820de12d66370bb2c2dc1ddad0e1df

commit 68487c7ca5820de12d66370bb2c2dc1ddad0e1df
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Tue Jul 17 11:09:32 2018 +0100

    Use indents in category view
    
    We keep around an empty imagelist for 1x1 images, to reset the indent when
    not in tree view mode
    
    (This isn't quite right as the listview will allocate a 1-pixel space before
    column 0 for those images, but this seems the best we can do as 0x0
    imagelists aren't allowed...)

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=db1c5abc5adae534968e941cef6e9286b96756e4

commit db1c5abc5adae534968e941cef6e9286b96756e4
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Tue Jul 17 19:26:23 2018 +0100

    Use an icon to represent expanded/collapsed state
    
    Use a listview icon to represent expanded/collapsed state, rather than
    puttting '[+]'/'[-]' in front of the category name
    
    Just use the item icon directly to represent expanded/collapsed state,
    rather than using a state icon as otherwise we have empty space where the
    item icon would be, when it's size is being used for indenting.
    
    The icons were made by tracing the previously used .bmp in Inkscape to
    produce a .svg, then converting that to an icon with ImageMagick using
    'convert -filter point -background transparent -define icon:auto-resize'
    
    v2:
    Drop execute mode (Achim Gratz)

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=b8461b560c32425026382b4f3f191be962b984ae

commit b8461b560c32425026382b4f3f191be962b984ae
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Mon Jul 16 20:02:25 2018 +0100

    Show the count of packages in a category

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=4018914c6c98b0d977554619d36b62f709e32ece

commit 4018914c6c98b0d977554619d36b62f709e32ece
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed Dec 7 19:52:58 2016 +0000

    Custom draw popup menus in ListView control
    
    Construct a menu containing the actions from the action list for the package
    or category, and if one is selected, apply it.
    
    This lets us remove packagemeta::set_action() which implements the strange
    UX of cycling around actions without showing what the possibilities are.
    
    This somewhat emulates a BS_SPLITBUTTON style control.  The 'popup' cell has
    a visual hint that clicking on it opens a menu (a 'combox scrollbar'), and
    the popup menu is located at the position of the click.
    
    v2:
    Factor out popup_menu, for future use by keyboard accelerators

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=8ba85a54652328a92c1eb68e898baf70ba05b525

commit 8ba85a54652328a92c1eb68e898baf70ba05b525
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Thu Dec 8 17:57:59 2016 +0000

    Add methods for listing possible actions on, and applying one to, a package
    
    The 'action id' is a value of the _actions enum, if positive, otherwise it's
    absolute value is a 0-based index into the 'versions' collection to identify
    a specific version to install.
    
    v2:
    Fix select_action() to consider deftrust
    
    v3:
    Fix select_action() for keep
    
    v4:
    Update for removed 'using namespace std'

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=61183f1540e036e69abb6347dc7bb134257d442c

commit 61183f1540e036e69abb6347dc7bb134257d442c
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed Dec 7 16:17:12 2016 +0000

    Custom draw checkboxes in ListView control
    
    Future work: What does this look like when a theme is in used? Does the row
    size need to be slightly adjusted to ensure it accomodates checkbox height?
    
    v2:
    erase to background colour before drawing checkbox

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=ce9f6dd0e4b7938c0919fe0efb06e4efe67242ca

commit ce9f6dd0e4b7938c0919fe0efb06e4efe67242ca
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed Dec 7 14:09:35 2016 +0000

    Use a ListView common control rather than a hand-built grid
    
    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()

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=ffc18a6962f5b2c7c3c06601c02c29f8d65866c7

commit ffc18a6962f5b2c7c3c06601c02c29f8d65866c7
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Fri Sep 16 19:23:26 2016 +0100

    Add OnNotify virtual function to class Window for WM_NOTIFY notifications
    
    Add OnNotify virtual function to class Window for WM_NOTIFY notifications
    Note that the result is returned via DWLP_MSGRESULT

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=9ae1ac43efb7d409583bf55537896e58a1812d4b

commit 9ae1ac43efb7d409583bf55537896e58a1812d4b
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sun Jul 15 18:02:08 2018 +0100

    Change packagemeta::_actions to an enum


Diff:
---
 ActionList.h        |   55 ++++
 ListView.cc         |  585 +++++++++++++++++++++++++++++++++
 ListView.h          |   96 ++++++
 Makefile.am         |   12 +-
 PickCategoryLine.cc |  165 +++-------
 PickCategoryLine.h  |   81 ++----
 PickLine.h          |   47 ---
 PickPackageLine.cc  |  150 +++++-----
 PickPackageLine.h   |   31 +-
 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 +-
 libsolv.cc          |   14 +
 libsolv.h           |    1 +
 main.cc             |    2 +-
 package_db.cc       |    2 +
 package_meta.cc     |  155 +++++-----
 package_meta.h      |   38 +--
 proppage.cc         |   14 +-
 res.rc              |   19 +-
 resource.h          |   13 +-
 tree-minus.bmp      |  Bin 106 -> 0 bytes
 tree-minus.ico      |  Bin 0 -> 299654 bytes
 tree-minus.svg      |  118 +++++++
 tree-plus.bmp       |  Bin 106 -> 0 bytes
 tree-plus.ico       |  Bin 0 -> 299671 bytes
 tree-plus.svg       |  126 ++++++++
 window.h            |    7 +
 33 files changed, 1606 insertions(+), 1307 deletions(-)

diff --git a/ActionList.h b/ActionList.h
new file mode 100644
index 0000000..2e2d424
--- /dev/null
+++ b/ActionList.h
@@ -0,0 +1,55 @@
+/*
+ * 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_ACTIONLIST_H
+#define SETUP_ACTIONLIST_H
+
+#include <string>
+#include <vector>
+
+// ---------------------------------------------------------------------------
+// interface to class ActionList
+//
+// a list of Actions possible on a package
+// ---------------------------------------------------------------------------
+
+class Action
+{
+ public:
+  Action(const std::string &_name, int _id, bool _selected, bool _enabled) :
+    name(_name),
+    id(_id),
+    selected(_selected),
+    enabled(_enabled)
+  { };
+
+  std::string name;
+  int id;
+  bool selected;
+  bool enabled;
+};
+
+typedef std::vector<Action> Actions;
+
+class ActionList
+{
+ public:
+  void add(const std::string &name, int id, bool selected, bool enabled)
+  {
+    Action act(name, id, selected, enabled);
+    list.push_back(act);
+  };
+  Actions list;
+};
+
+#endif /* SETUP_ACTIONLIST_H */
diff --git a/ListView.cc b/ListView.cc
new file mode 100644
index 0000000..e287270
--- /dev/null
+++ b/ListView.cc
@@ -0,0 +1,585 @@
+/*
+ * 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 "resource.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);
+
+  // create a small icon imagelist
+  // (the order of images matches ListViewLine::State enum)
+  hImgList = ImageList_Create(GetSystemMetrics(SM_CXSMICON),
+                              GetSystemMetrics(SM_CYSMICON),
+                              ILC_COLOR32, 2, 0);
+  ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_PLUS)));
+  ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_MINUS)));
+
+  // create an empty imagelist, used to reset the indent
+  hEmptyImgList = ImageList_Create(1, 1,
+                                   ILC_COLOR32, 2, 0);
+
+  // LVS_EX_INFOTIP/LVN_GETINFOTIP doesn't work for subitems, so we have to do
+  // our own tooltip handling
+  hWndTip = CreateWindowEx (0,
+                            (LPCTSTR) TOOLTIPS_CLASS,
+                            NULL,
+                            WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
+                            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+                            hWndParent,
+                            (HMENU) 0,
+                            GetModuleHandle(NULL),
+                            NULL);
+  // must be topmost so that tooltips will display on top
+  SetWindowPos(hWndTip, HWND_TOPMOST,0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+
+  TOOLINFO ti;
+  memset ((void *)&ti, 0, sizeof(ti));
+  ti.cbSize = sizeof(ti);
+  ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
+  ti.hwnd = hWndParent;
+  ti.uId = (UINT_PTR)hWndListView;
+  ti.lpszText =  LPSTR_TEXTCALLBACK; // use TTN_GETDISPINFO
+  SendMessage(hWndTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
+
+  // match long delay for tooltip to disappear used elsewhere (30s)
+  SendMessage(hWndTip, TTM_SETDELAYTIME, TTDT_AUTOPOP, (LPARAM) MAKELONG (30000, 0));
+  // match tip width used elsewhere
+  SendMessage(hWndTip, TTM_SETMAXTIPWIDTH, 0, 450);
+}
+
+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, bool tree)
+{
+  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();
+
+  // assign imagelist to listview control (this also sets the size for indents)
+  if (tree)
+    ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL);
+  else
+    ListView_SetImageList(hWndListView, hEmptyImgList, LVSIL_SMALL);
+
+  size_t i;
+  for (i = 0; i < contents->size();  i++)
+    {
+      LVITEM lvi;
+      lvi.mask = LVIF_TEXT | (tree ? LVIF_IMAGE | LVIF_INDENT : 0);
+      lvi.iItem = i;
+      lvi.iSubItem = 0;
+      lvi.pszText = LPSTR_TEXTCALLBACK;
+      if (tree)
+        {
+          lvi.iImage = I_IMAGECALLBACK;
+          lvi.iIndent = (*contents)[i]->get_indent();
+        }
+
+      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;
+
+          if (pNmLvDispInfo->item.iSubItem == 0)
+            {
+              pNmLvDispInfo->item.iImage = (int)((*contents)[pNmLvDispInfo->item.iItem]->get_state());
+            }
+        }
+
+      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)
+        return false;
+
+      int update = 0;
+
+      if (headers[iCol].type == ListView::ControlType::popup)
+        {
+          POINT p;
+          // position pop-up menu at the location of the click
+          GetCursorPos(&p);
+
+          update = popup_menu(iRow, iCol, p);
+        }
+      else
+        {
+          // Inform the item of the click
+          update = (*contents)[iRow]->do_action(iCol, 0);
+        }
+
+      // Update items, if needed
+      if (update > 0)
+        {
+          ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
+        }
+
+      return true;
+    }
+    break;
+
+  case NM_CUSTOMDRAW:
+    {
+      NMLVCUSTOMDRAW *pNmLvCustomDraw = (NMLVCUSTOMDRAW *)pNmHdr;
+
+      switch(pNmLvCustomDraw->nmcd.dwDrawStage)
+        {
+        case CDDS_PREPAINT:
+          *pResult = CDRF_NOTIFYITEMDRAW;
+          return true;
+        case CDDS_ITEMPREPAINT:
+          *pResult = CDRF_NOTIFYSUBITEMDRAW;
+          return true;
+        case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
+          {
+            LRESULT result = CDRF_DODEFAULT;
+            int iCol = pNmLvCustomDraw->iSubItem;
+            int iRow = pNmLvCustomDraw->nmcd.dwItemSpec;
+
+            switch (headers[iCol].type)
+              {
+              default:
+              case ListView::ControlType::text:
+                result = CDRF_DODEFAULT;
+                break;
+
+              case ListView::ControlType::checkbox:
+                {
+                  // get the subitem text
+                  char buf[3];
+                  ListView_GetItemText(hWndListView, iRow, iCol, buf, _countof(buf));
+
+                  // map the subitem text to a checkbox state
+                  UINT state = DFCS_BUTTONCHECK | DFCS_FLAT;
+                  if (buf[0] == '\0')                           // empty
+                    {
+                      result = CDRF_DODEFAULT;
+                      break;
+                    }
+                  else if (buf[0] == 'y')                       // yes
+                    state |= DFCS_CHECKED;
+                  else if ((buf[0] == 'n') && (buf[1] == 'o'))  // no
+                    state |= 0;
+                  else                                          // n/a
+                    state |= DFCS_INACTIVE;
+
+                  // erase and draw a checkbox
+                  RECT r;
+                  ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r);
+                  HBRUSH hBrush = CreateSolidBrush(ListView_GetBkColor(hWndListView));
+                  FillRect(pNmLvCustomDraw->nmcd.hdc, &r, hBrush);
+                  DeleteObject(hBrush);
+                  DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_BUTTON, state);
+
+                  result = CDRF_SKIPDEFAULT;
+                }
+                break;
+
+              case ListView::ControlType::popup:
+                {
+                  // let the control draw the text, but notify us afterwards
+                  result = CDRF_NOTIFYPOSTPAINT;
+                }
+                break;
+              }
+
+            *pResult = result;
+            return true;
+          }
+        case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT:
+          {
+            LRESULT result = CDRF_DODEFAULT;
+            int iCol = pNmLvCustomDraw->iSubItem;
+            int iRow = pNmLvCustomDraw->nmcd.dwItemSpec;
+
+            switch (headers[iCol].type)
+              {
+              default:
+                result = CDRF_DODEFAULT;
+                break;
+
+              case ListView::ControlType::popup:
+                {
+                  // draw the control at the RHS of the cell
+                  RECT r;
+                  ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r);
+                  r.left = r.right - GetSystemMetrics(SM_CXVSCROLL);
+                  DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_SCROLL,DFCS_SCROLLCOMBOBOX);
+
+                  result = CDRF_DODEFAULT;
+                }
+                break;
+              }
+            *pResult = result;
+            return true;
+          }
+        }
+    }
+    break;
+
+  case LVN_HOTTRACK:
+    {
+      NMLISTVIEW *pNmListView = (NMLISTVIEW *)pNmHdr;
+      int iRow = pNmListView->iItem;
+      int iCol = pNmListView->iSubItem;
+#if DEBUG
+      Log (LOG_BABBLE) << "LVN_HOTTRACK " << iRow << " " << iCol << endLog;
+#endif
+      if (iRow < 0)
+        return true;
+
+      // if we've tracked off to a different cell
+      if ((iRow != iRow_track) || (iCol != iCol_track))
+        {
+#if DEBUG
+          Log (LOG_BABBLE) << "LVN_HOTTRACK changed cell" << endLog;
+#endif
+
+          // if the tooltip for previous cell is displayed, remove it
+          // restart the tooltip AUTOPOP timer for this cell
+          SendMessage(hWndTip, TTM_ACTIVATE, FALSE, 0);
+          SendMessage(hWndTip, TTM_ACTIVATE, TRUE, 0);
+
+          iRow_track = iRow;
+          iCol_track = iCol;
+        }
+
+      return true;
+    }
+    break;
+
+  case TTN_GETDISPINFO:
+    {
+      // convert mouse position to item/subitem
+      LVHITTESTINFO lvHitTestInfo;
+      lvHitTestInfo.flags = LVHT_ONITEM;
+      GetCursorPos(&lvHitTestInfo.pt);
+      ::ScreenToClient(hWndListView, &lvHitTestInfo.pt);
+      ListView_SubItemHitTest(hWndListView, &lvHitTestInfo);
+
+      int iRow = lvHitTestInfo.iItem;
+      int iCol = lvHitTestInfo.iSubItem;
+      if (iRow < 0)
+        return false;
+
+#if DEBUG
+      Log (LOG_BABBLE) << "TTN_GETDISPINFO " << iRow << " " << iCol << endLog;
+#endif
+
+      // get the tooltip text for that item/subitem
+      static StringCache tooltip;
+      tooltip = "";
+      if (contents)
+        tooltip = (*contents)[iRow]->get_tooltip(iCol);
+
+      // set the tooltip text
+      NMTTDISPINFO *pNmTTDispInfo = (NMTTDISPINFO *)pNmHdr;
+      pNmTTDispInfo->lpszText = tooltip;
+      pNmTTDispInfo->hinst = NULL;
+      pNmTTDispInfo->uFlags = 0;
+
+      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;
+}
+
+int
+ListView::popup_menu(int iRow, int iCol, POINT p)
+{
+  int update = 0;
+  // construct menu
+  HMENU hMenu = CreatePopupMenu();
+
+  MENUITEMINFO mii;
+  memset(&mii, 0, sizeof(mii));
+  mii.cbSize = sizeof(mii);
+  mii.fMask = MIIM_FTYPE | MIIM_STATE | MIIM_STRING | MIIM_ID;
+  mii.fType = MFT_STRING;
+
+  ActionList *al = (*contents)[iRow]->get_actions(iCol);
+
+  Actions::iterator i;
+  int j = 1;
+  for (i = al->list.begin (); i != al->list.end (); ++i, ++j)
+    {
+      BOOL res;
+      mii.dwTypeData = (char *)i->name.c_str();
+      mii.fState = (i->selected ? MFS_CHECKED : MFS_UNCHECKED |
+                    i->enabled ? MFS_ENABLED : MFS_DISABLED);
+      mii.wID = j;
+
+      res = InsertMenuItem(hMenu, -1, TRUE, &mii);
+      if (!res) Log (LOG_BABBLE) << "InsertMenuItem failed " << endLog;
+    }
+
+  int id = TrackPopupMenu(hMenu,
+                          TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_NOANIMATION,
+                          p.x, p.y, 0, hWndListView, NULL);
+
+  // Inform the item of the menu choice
+  if (id)
+    update = (*contents)[iRow]->do_action(iCol, al->list[id-1].id);
+
+  DestroyMenu(hMenu);
+  delete al;
+
+  return update;
+}
diff --git a/ListView.h b/ListView.h
new file mode 100644
index 0000000..b4fd1fd
--- /dev/null
+++ b/ListView.h
@@ -0,0 +1,96 @@
+/*
+ * 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 "ActionList.h"
+#include "win32.h"
+#include <commctrl.h>
+#include <vector>
+
+// ---------------------------------------------------------------------------
+// interface to class ListView
+//
+// ListView Common Control
+// ---------------------------------------------------------------------------
+
+class ListViewLine
+{
+ public:
+  enum class State { collapsed, expanded, nothing=-1 };
+
+  virtual ~ListViewLine() {};
+  virtual const std::string get_text(int col) const = 0;
+  virtual State get_state() const = 0;
+  virtual const std::string get_tooltip(int col) const = 0;
+  virtual int get_indent() const = 0;
+  virtual ActionList *get_actions(int col) const = 0;
+  virtual int do_action(int col, int id) = 0;
+};
+
+typedef std::vector<ListViewLine *> ListViewContents;
+
+class ListView
+{
+ public:
+  enum class ControlType
+  {
+    text,
+    checkbox,
+    popup,
+  };
+
+  class Header
+  {
+  public:
+    const char *text;
+    int fmt;
+    ControlType type;
+    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, bool tree = false);
+  void setEmptyText(const char *text);
+
+  bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult);
+
+ private:
+  HWND hWndParent;
+  HWND hWndListView;
+  HWND hWndTip;
+  HDC dc;
+  HIMAGELIST hImgList;
+  HIMAGELIST hEmptyImgList;
+
+  ListViewContents *contents;
+  HeaderList headers;
+  const char *empty_list_text;
+  int iRow_track;
+  int iCol_track;
+
+  void initColumns(HeaderList hl);
+  void empty(void);
+  int popup_menu(int iRow, int iCol, POINT p);
+};
+
+#endif /* SETUP_LISTVIEW_H */
diff --git a/Makefile.am b/Makefile.am
index 0ad4e5c..3c41389 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -43,17 +43,13 @@ 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
+	tree-minus.ico \
+	tree-plus.ico
 
 # 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
@@ -108,6 +104,7 @@ inilint_SOURCES = \
 	-lshlwapi -lcomctl32 -lole32 -lpsapi -luuid -lntdll -lwininet -lws2_32 -lmingw32
 @SETUP@_LDFLAGS = -mwindows -Wc,-static -static-libtool-libs
 @SETUP@_SOURCES = \
+	actionlist.h \
 	AntiVirus.cc \
 	AntiVirus.h \
 	archive.cc \
@@ -181,6 +178,8 @@ inilint_SOURCES = \
 	KeysSetting.h \
 	libsolv.cc \
 	libsolv.h \
+	ListView.cc \
+	ListView.h \
 	localdir.cc \
 	localdir.h \
 	LogFile.cc \
@@ -216,7 +215,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 24fecb3..1dbecf2 100644
--- a/PickCategoryLine.cc
+++ b/PickCategoryLine.cc
@@ -16,134 +16,75 @@
 #include "PickCategoryLine.h"
 #include "package_db.h"
 #include "PickView.h"
+#include "window.h"
+#include "package_meta.h"
+#include <sstream>
 
-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::ostringstream s;
+      s << cat_tree->category().first;
+      if (pkgcount)
+        s << " (" << pkgcount << ")";
+      return s.str();
     }
+  else if (col_num == new_col)
+    {
+      return packagemeta::action_caption (cat_tree->action());
+    }
+  return "";
 }
 
-void
-PickCategoryLine::paint (HDC hdc, HRGN hUpdRgn, int x, int y, int row, int show_cat)
+int
+PickCategoryLine::do_action(int col_num, int action_id)
 {
-  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, 
-               current_default.caption (), strlen (current_default.caption ()));
-      row++;
-    }
-  if (collapsed)
-    return;
-  
-  // are the siblings containers?
-  if (bucket.size () && bucket[0]->IsContainer ())
+  if (col_num == pkgname_col)
     {
-      for (size_t n = 0; n < bucket.size (); n++)
-        {
-          bucket[n]->paint (hdc, hUpdRgn, x, y, row, show_cat);
-          row += bucket[n]->itemcount ();
-        }
+      cat_tree->collapsed() = ! cat_tree->collapsed();
+      theView.refresh();
     }
-  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);
-        }
+      theView.GetParent ()->SetBusy ();
+      int u = cat_tree->do_action((packagemeta::_actions)(action_id), theView.deftrust);
+      theView.GetParent ()->ClearBusy ();
+      return u;
     }
+  return 1;
+}
+
+ActionList *
+PickCategoryLine::get_actions(int col) const
+{
+  ActionList *al = new ActionList();
+  packagemeta::_actions current_default = cat_tree->action();
+
+  al->add("Default", (int)packagemeta::Default_action, (current_default == packagemeta::Default_action), TRUE);
+  al->add("Install", (int)packagemeta::Install_action, (current_default == packagemeta::Install_action), TRUE);
+  al->add(packagedb::task == PackageDB_Install ? "Reinstall" : "Retrieve",
+          (int)packagemeta::Reinstall_action, (current_default == packagemeta::Reinstall_action), TRUE);
+  al->add("Uninstall", (int)packagemeta::Uninstall_action, (current_default == packagemeta::Uninstall_action), TRUE);
+
+  return al;
+}
+
+ListViewLine::State
+PickCategoryLine::get_state() const
+{
+  return cat_tree->collapsed() ? State::collapsed : State::expanded;
 }
 
 int
-PickCategoryLine::click (int const myrow, int const ClickedRow, int const x)
+PickCategoryLine::get_indent() const
 {
-  if (myrow == ClickedRow && show_label)
-    {
-      if ((size_t) x >= spin_x)
-	{
-	  ++current_default;
-	  
-	  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;
-	}
-    }
-  else
-    {
-      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;
-    }
+  return indent;
 }
 
-int 
-PickCategoryLine::set_action (packagemeta::_actions action)
+const std::string
+PickCategoryLine::get_tooltip(int col_num) const
 {
-  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 "";
 }
diff --git a/PickCategoryLine.h b/PickCategoryLine.h
index dcffbac..9c5e996 100644
--- a/PickCategoryLine.h
+++ b/PickCategoryLine.h
@@ -16,75 +16,36 @@
 #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, int _pkgcount, int _indent) :
+    cat_tree (_tree),
+    pkgcount(_pkgcount),
+    theView (aView),
+    indent(_indent)
   {
-    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;
+  State get_state() const;
+  const std::string get_tooltip(int col) const;
+  int get_indent() const;
+  ActionList *get_actions(int col) const;
+  int do_action(int col, int action_id);
+
 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;
+  int pkgcount;
+  PickView & theView;
+  int indent;
 };
+
 #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 <robertc@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..133720b 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,76 @@ 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 == pkg_col)
+    {
+      return pkg.SDesc();
     }
-  else if (col_num == theView.pkg_col)
+
+  return "unknown";
+}
+
+const std::string
+PickPackageLine::get_tooltip(int col_num) const
+{
+  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.LDesc();
     }
+
+  return "";
 }
 
 int
-PickPackageLine::click (int const myrow, int const ClickedRow, int const x)
+PickPackageLine::do_action(int col_num, int action_id)
 {
-  // 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;
+      pkg.select_action(action_id, theView.deftrust);
+      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)
+ActionList *
+PickPackageLine::get_actions(int col_num) const
+{
+  if (col_num == new_col)
+    {
+      return pkg.list_actions (theView.deftrust);
+    }
+
+  return NULL;
+}
+
+int
+PickPackageLine::get_indent() const
 {
-  pkg.set_action (action, pkg.trustp (true, theView.deftrust));
-  return 1;
+  return indent;
 }
diff --git a/PickPackageLine.h b/PickPackageLine.h
index 612bf38..12c7636 100644
--- a/PickPackageLine.h
+++ b/PickPackageLine.h
@@ -17,32 +17,29 @@
 #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, int aindent) :
+    pkg (apkg),
+    theView (aView),
+    indent (aindent)
   {
   };
-  virtual int set_action (packagemeta::_actions);
+  const std::string get_text(int col) const;
+  State get_state() const { return State::nothing; }
+  const std::string get_tooltip(int col) const;
+  int get_indent() const;
+  ActionList *get_actions(int col_num) const;
+  int do_action(int col, int action_id);
 private:
   packagemeta & pkg;
   PickView & theView;
+  int indent;
 };
 
 #endif /* SETUP_PICKPACKAGELINE_H */
diff --git a/PickView.cc b/PickView.cc
index f8875e4..8412282 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, view_mode == PickView::views::Category);
 }
 
 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)
+PickView::insert_pkg (packagemeta & pkg, int indent)
 {
   if (!showObsolete && isObsolete (pkg.categories))
     return;
 
-  PickLine & line = *new PickPackageLine (*this, pkg);
-  contents.insert (line);
+  contents.push_back(new PickPackageLine(*this, pkg, indent));
 }
 
 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);
+
+  const Category *cat = &(cat_tree->category());
+
+  // Suppress obsolete category when not showing obsolete
+  if ((!showObsolete && isObsolete (cat->first)))
+    return;
+
+  // if it's not the "All" category
   int packageCount = 0;
-  for (std::vector <packagemeta *>::iterator i = cat->second.begin ();
-       i != cat->second.end () ; ++i)
+  bool hasContents = false;
+  bool isAll = casecompare(cat->first, "All") == 0;
+  if (!isAll)
     {
-      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;
-}
+      // count the number of packages in this category
+      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++;
+            }
+        }
 
-int
-PickView::click (int row, int x)
-{
-  return contents.click (0, row, x);
-}
+      // if there are some packages in the category, or we are showing everything,
+      if (packageFilterString.empty () || packageCount)
+        {
+          hasContents = true;
+        }
+    }
 
+  if (!isAll && !hasContents)
+    return;
 
-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);
+  // insert line for the category
+  contents.push_back(new PickCategoryLine(*this, cat_tree, packageCount, isAll ? 0 : 1));
 
-  switch (code)
+  // if not collapsed
+  if (!cat_tree->collapsed())
     {
-    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;
-    }
+      // 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, 2);
+                }
+            }
+        }
 
-  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)
-    {
-      ::GetClientRect (listheader, &cr);
-      sr = cr;
-//  UpdateWindow (htmp);
-      ::MoveWindow (listheader, -scroll_ulc_x, 0,
-                  headers[last_col].x +
-                  headers[last_col].width, header_height, TRUE);
+      // 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;
+  listview->noteColumnWidthStart();
 
-  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);
+  // widths of the 'bin' and 'src' checkbox columns just need to accommodate the
+  // column name
+  listview->noteColumnWidth (bintick_col, "");
+  listview->noteColumnWidth (srctick_col, "");
 
-  // 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);
-
-  // 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)
-{
-  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);
-}
-
-LRESULT CALLBACK
-PickView::list_click (HWND hwnd, BOOL dblclk, int x, int y, UINT hitCode)
+PickView::build_category_tree()
 {
-  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;
-
-  if (row < 0 || row >= contents.itemcount ())
-    return 0;
+  /* Build the category tree */
 
-  refresh = click (row, x);
+  /* Start collapsed. TODO: make that a flag */
+  bool collapsed = true;
 
-  // 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..3a6c602 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 &, int indent = 0);
+  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)->select_action(action_id, 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 c139e54..0000000
Binary files a/check-na.bmp and /dev/null differ
diff --git a/check-no.bmp b/check-no.bmp
deleted file mode 100644
index 3639605..0000000
Binary files a/check-no.bmp and /dev/null differ
diff --git a/check-yes.bmp b/check-yes.bmp
deleted file mode 100644
index f328dc2..0000000
Binary files a/check-yes.bmp and /dev/null differ
diff --git a/choose-spin.bmp b/choose-spin.bmp
deleted file mode 100644
index 8779f6d..0000000
Binary files a/choose-spin.bmp and /dev/null differ
diff --git a/choose.cc b/choose.cc
index 98671c1..2b5e465 100644
--- a/choose.cc
+++ b/choose.cc
@@ -79,7 +79,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},
@@ -130,23 +129,33 @@ ChooserPage::~ChooserPage ()
     }
 }
 
+static ListView::Header pkg_headers[] = {
+  {"Package",     LVCFMT_LEFT,  ListView::ControlType::text},
+  {"Current",     LVCFMT_LEFT,  ListView::ControlType::text},
+  {"New",         LVCFMT_LEFT,  ListView::ControlType::popup},
+  {"Bin?",        LVCFMT_LEFT,  ListView::ControlType::checkbox},
+  {"Src?",        LVCFMT_LEFT,  ListView::ControlType::checkbox},
+  {"Categories",  LVCFMT_LEFT,  ListView::ControlType::text},
+  {"Size",        LVCFMT_RIGHT, ListView::ControlType::text},
+  {"Description", LVCFMT_LEFT,  ListView::ControlType::text},
+  {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 ();
 }
 
@@ -238,16 +247,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 ()
 {
@@ -334,6 +333,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();
@@ -445,7 +455,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)
@@ -547,10 +557,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/libsolv.cc b/libsolv.cc
index 955a1b2..ba54fc5 100644
--- a/libsolv.cc
+++ b/libsolv.cc
@@ -193,6 +193,20 @@ SolvableVersion::SDesc () const
 }
 
 const std::string
+SolvableVersion::LDesc () const
+{
+  if (!id)
+    return "";
+  Solvable *solvable = pool_id2solvable(pool, id);
+  const char *ldesc = repo_lookup_str(solvable->repo, id, SOLVABLE_DESCRIPTION);
+
+  if (!ldesc)
+    return "";
+
+  return ldesc;
+}
+
+const std::string
 SolvableVersion::sourcePackageName () const
 {
   if (!id)
diff --git a/libsolv.h b/libsolv.h
index f394e65..2eb1f24 100644
--- a/libsolv.h
+++ b/libsolv.h
@@ -54,6 +54,7 @@ class SolvableVersion
 
   const std::string Name () const;
   const std::string SDesc () const;
+  const std::string LDesc () const;
   // In setup-speak, 'Canonical' version means 'e:v-r', the non-decomposed version
   const std::string Canonical_version () const;
   // Return the dependency list
diff --git a/main.cc b/main.cc
index caa213f..749b00c 100644
--- a/main.cc
+++ b/main.cc
@@ -143,7 +143,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/package_db.cc b/package_db.cc
index 8b8e120..59c59ef 100644
--- a/package_db.cc
+++ b/package_db.cc
@@ -147,6 +147,7 @@ packagedb::read ()
                   if (pv)
                     {
                       data.sdesc = pv.SDesc();
+                      data.ldesc = pv.LDesc();
                       data.archive = *pv.source();
                       data.stability = pv.Stability();
                       data.spkg_id = pv.sourcePackage();
@@ -170,6 +171,7 @@ packagedb::read ()
                       if (pkgm)
                         {
                           data.sdesc = pkgm->curr.SDesc();
+                          data.ldesc = pkgm->curr.LDesc();
                           if (pkgm->curr
                               && version_compare (f.ver, pkgm->curr.Canonical_version()) > 0)
                             data.stability = TRUST_TEST;
diff --git a/package_meta.cc b/package_meta.cc
index ab1e175..9880bcb 100644
--- a/package_meta.cc
+++ b/package_meta.cc
@@ -51,35 +51,22 @@ bool hasManualSelections = 0;
 
 /*****************/
 
-const
-  packagemeta::_actions
-packagemeta::Default_action (0);
-const
-  packagemeta::_actions
-packagemeta::Install_action (1);
-const
-  packagemeta::_actions
-packagemeta::Reinstall_action (2);
-const
-  packagemeta::_actions
-packagemeta::Uninstall_action (3);
-
 char const *
-packagemeta::_actions::caption ()
+packagemeta::action_caption (_actions _value)
 {
   switch (_value)
     {
-    case 0:
+    case Default_action:
       return "Default";
-    case 1:
+    case Install_action:
       return "Install";
-    case 2:
+    case Reinstall_action:
       return "Reinstall";
-    case 3:
+    case Uninstall_action:
       return "Uninstall";
     }
-  // Pacify GCC: (all case options are checked above)
-  return 0;
+
+  return "Unknown";
 }
 
 packagemeta::packagemeta (packagemeta const &rhs) :
@@ -93,14 +80,6 @@ packagemeta::packagemeta (packagemeta const &rhs) :
 
 }
 
-packagemeta::_actions & packagemeta::_actions::operator++ ()
-{
-  ++_value;
-  if (_value > 3)
-    _value = 0;
-  return *this;
-}
-
 template<class T> struct removeCategory : public std::unary_function<T, void>
 {
   removeCategory(packagemeta *pkg) : _pkg (pkg) {}
@@ -427,6 +406,21 @@ packagemeta::SDesc () const
   return std::string();
 }
 
+static bool
+hasLDesc(packageversion const &pkg)
+{
+  return pkg.LDesc().size();
+}
+
+const std::string
+packagemeta::LDesc () const
+{
+  std::set<packageversion>::iterator i = find_if (versions.begin(), versions.end(), hasLDesc);
+  if (i == versions.end())
+    return std::string();
+  return i->LDesc ();
+};
+
 /* Return an appropriate caption given the current action. */
 std::string
 packagemeta::action_caption () const
@@ -446,63 +440,24 @@ packagemeta::action_caption () const
     return desired.Canonical_version ();
 }
 
-/* Set the next action given a current action.  */
 void
-packagemeta::set_action (trusts const trust)
+packagemeta::select_action (int id, trusts const deftrust)
 {
-  std::set<packageversion>::iterator i;
-
-  /* Keep the picked settings of the former desired version, if any, and make
-     sure at least one of them is picked.  If both are unpicked, pick the
-     binary version. */
-  bool source_picked = desired && srcpicked ();
-  bool binary_picked = !desired || picked () || !source_picked;
-
-  /* If we're on "Keep" on the installed version, and the version is available,
-     switch to "Reinstall". */
-  if (desired && desired == installed && !picked ()
-      && desired.accessible ())
+  if (id <= 0)
     {
-      pick (true);
-      return;
-    }
+      // Install a specific version
+      std::set<packageversion>::iterator i = versions.begin ();
+      for (int j = -id; j > 0; j--)
+        i++;
 
-  if (!desired)
-    {
-      /* From "Uninstall" switch to the first version.  From "Skip" switch to
-         the first version as well, unless the user picks for the first time.
-	 In that case switch to the trustp version immediately. */
-      if (installed || user_picked)
-	i = versions.begin ();
-      else
-	for (i = versions.begin ();
-	     i != versions.end () && *i != trustp (false, trust);
-	     ++i)
-	  ;
-    }
-  else
-    {
-      /* Otherwise switch to the next version. */
-      for (i = versions.begin (); i != versions.end () && *i != desired; ++i)
-	;
-      ++i;
-    }
-  /* If there's another version in the list, switch to it, otherwise
-     switch to "Uninstall". */
-  if (i != versions.end ())
-    {
-      desired = *i;
-      /* If the next version is the installed version, unpick it.  This will
-	 have the desired effect to show the package in "Keep" mode.  See also
-	 above for the code switching to "Reinstall". */
-      pick (desired != installed && binary_picked);
-      srcpick (desired.sourcePackage().accessible () && source_picked);
+      set_action(Install_action, *i);
     }
   else
     {
-      desired = packageversion ();
-      pick(false);
-      srcpick(false);
+      if (id == packagemeta::Default_action)
+        set_action((packagemeta::_actions)id, installed);
+      else
+        set_action((packagemeta::_actions)id, trustp (true, deftrust));
     }
 
   /* Memorize the fact that the user picked at least once. */
@@ -510,6 +465,50 @@ packagemeta::set_action (trusts const trust)
     user_picked = true;
 }
 
+ActionList *
+packagemeta::list_actions(trusts const trust)
+{
+  // first work out the current action, so we can indicate that
+  _actions action;
+
+  if (!desired && installed)
+    action = Uninstall_action;
+  else if (!desired)
+    action = Default_action; // skip
+  else if (desired == installed && picked())
+    action = Reinstall_action;
+  else if (desired == installed)
+    action = Default_action; // keep
+  else
+    action = Install_action;
+
+  // now build the list of possible actions
+  ActionList *al = new ActionList();
+
+  al->add("Uninstall", (int)Uninstall_action, (action == Uninstall_action), bool(installed));
+  al->add("Skip", (int)Default_action, (action == Default_action) && !installed, !installed);
+
+  std::set<packageversion>::iterator i;
+  for (i = versions.begin (); i != versions.end (); ++i)
+    {
+      if (*i == installed)
+        {
+          al->add("Keep", (int)Default_action, (action == Default_action), TRUE);
+          al->add(packagedb::task == PackageDB_Install ? "Reinstall" : "Retrieve",
+                  (int)Reinstall_action, (action == Reinstall_action), TRUE);
+        }
+      else
+        {
+          al->add(i->Canonical_version().c_str(),
+                  -std::distance(versions.begin (), i),
+                  (action == Install_action) && (*i == desired),
+                  TRUE);
+        }
+    }
+
+  return al;
+}
+
 // Set a particular type of action.
 void
 packagemeta::set_action (_actions action, packageversion const &default_version)
diff --git a/package_meta.h b/package_meta.h
index 8db10e2..0eff8d0 100644
--- a/package_meta.h
+++ b/package_meta.h
@@ -26,6 +26,7 @@ class packagemeta;
 #include "package_version.h"
 #include "package_message.h"
 #include "script.h"
+#include "ActionList.h"
 
 typedef std::pair<const std::string, std::vector<packagemeta *> > Category;
 
@@ -50,28 +51,18 @@ public:
   void setDefaultCategories();
   void addToCategoryAll();
 
-  class _actions
-  {
-  public:
-    _actions ():_value (0) {};
-    _actions (int aInt) {
-    _value = aInt;
-    if (_value < 0 ||  _value > 3)
-      _value = 0;
-    }
-    _actions & operator ++ ();
-    bool operator == (_actions const &rhs) { return _value == rhs._value; }
-    bool operator != (_actions const &rhs) { return _value != rhs._value; }
-    const char *caption ();
-  private:
-    int _value;
-  };
-  static const _actions Default_action;
-  static const _actions Install_action;
-  static const _actions Reinstall_action;
-  static const _actions Uninstall_action;
-  void set_action (trusts const t);
+  enum _actions
+    {
+     Default_action = 1,
+     Install_action,
+     Reinstall_action,
+     Uninstall_action,
+    };
+  static const char *action_caption (_actions value);
+
   void set_action (_actions, packageversion const & default_version);
+  ActionList *list_actions(trusts const trust);
+  void select_action (int id, trusts const deftrust);
 
   void set_message (const std::string& message_id, const std::string& message_string)
   {
@@ -117,9 +108,10 @@ public:
   bool isManuallyWanted() const;
   /* true if package was deleted on command-line. */
   bool isManuallyDeleted() const;
-  /* SDesc is global in theory, across all package versions. 
-     LDesc is not: it can be different per version */
+
   const std::string SDesc () const;
+  const std::string LDesc () const;
+
   /* what categories does this package belong in. Note that if multiple versions
    * of a package disagree.... the first one read in will take precedence.
    */
diff --git a/proppage.cc b/proppage.cc
index 6b83640..8da1c52 100644
--- a/proppage.cc
+++ b/proppage.cc
@@ -140,7 +140,18 @@ PropertyPage::DialogProc (UINT message, WPARAM wParam, LPARAM lParam)
           return TRUE;
         }
       case WM_NOTIFY:
-        switch (((NMHDR FAR *) lParam)->code)
+        {
+        NMHDR *pNmHdr = (NMHDR *) lParam;
+
+        // offer to subclass first
+        LRESULT result = 0;
+        if (OnNotify (pNmHdr, &result))
+          {
+            SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, result);
+            return TRUE;
+          }
+
+        switch (pNmHdr->code)
         {
           case PSN_APPLY:
             {
@@ -261,6 +272,7 @@ PropertyPage::DialogProc (UINT message, WPARAM wParam, LPARAM lParam)
               return FALSE;
             }
         }
+        }
         break;
       case WM_COMMAND:
         {
diff --git a/res.rc b/res.rc
index 02b60cf..10f20ba 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
@@ -518,6 +519,8 @@ CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "setup.exe.manifest"
 IDI_CYGWIN_SETUP        ICON    DISCARDABLE     "cygwin-setup.ico"
 IDI_CYGWIN              ICON    DISCARDABLE     "cygwin.ico"
 IDI_CYGWIN_TERMINAL     ICON    DISCARDABLE     "cygwin-terminal.ico"
+IDI_TREE_PLUS           ICON    DISCARDABLE     "tree-plus.ico"
+IDI_TREE_MINUS          ICON    DISCARDABLE     "tree-minus.ico"
 
 /////////////////////////////////////////////////////////////////////////////
 //
@@ -530,18 +533,6 @@ 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..852bdc0 100644
--- a/resource.h
+++ b/resource.h
@@ -71,20 +71,13 @@
 #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
 #define IDI_CYGWIN                        402
 #define IDI_CYGWIN_TERMINAL               403
+#define IDI_TREE_PLUS                     404
+#define IDI_TREE_MINUS                    405
 
 // controls
 
@@ -118,7 +111,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 +127,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 35b2221..0000000
Binary files a/tree-minus.bmp and /dev/null differ
diff --git a/tree-minus.ico b/tree-minus.ico
new file mode 100644
index 0000000..46fd3b1
Binary files /dev/null and b/tree-minus.ico differ
diff --git a/tree-minus.svg b/tree-minus.svg
new file mode 100644
index 0000000..124918b
--- /dev/null
+++ b/tree-minus.svg
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="256px"
+   height="256px"
+   viewBox="0 0 256 256"
+   version="1.1"
+   id="SVGRoot"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="tree-minus.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="2.3754368"
+     inkscape:cx="135.00514"
+     inkscape:cy="129.49343"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1680"
+     inkscape:window-height="1027"
+     inkscape:window-x="1912"
+     inkscape:window-y="22"
+     inkscape:window-maximized="1"
+     inkscape:grid-bbox="true"
+     showguides="false"
+     inkscape:object-nodes="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4075" />
+  </sodipodi:namedview>
+  <defs
+     id="defs3756" />
+  <metadata
+     id="metadata3759">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:groupmode="layer"
+     inkscape:label="layer">
+    <g
+       id="box"
+       inkscape:label="box"
+       style="fill:#000000;fill-opacity:1"
+       transform="translate(8,8)">
+      <rect
+         y="56"
+         x="56"
+         height="16"
+         width="144"
+         id="top"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="top" />
+      <rect
+         y="184"
+         x="56"
+         height="16"
+         width="144"
+         id="bottom"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="bottom" />
+      <rect
+         transform="rotate(90)"
+         y="-72"
+         x="56"
+         height="16"
+         width="144"
+         id="left"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="left" />
+      <rect
+         transform="rotate(90)"
+         y="-200"
+         x="56"
+         height="16"
+         width="144"
+         id="right"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="right" />
+    </g>
+    <g
+       id="symbol"
+       inkscape:label="symbol"
+       style="fill:#000000;fill-opacity:1"
+       transform="translate(8,8)">
+      <rect
+         transform="rotate(90)"
+         y="-168"
+         x="120"
+         height="80"
+         width="16"
+         id="horiz"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="horiz" />
+    </g>
+  </g>
+</svg>
diff --git a/tree-plus.bmp b/tree-plus.bmp
deleted file mode 100644
index e0335d9..0000000
Binary files a/tree-plus.bmp and /dev/null differ
diff --git a/tree-plus.ico b/tree-plus.ico
new file mode 100644
index 0000000..8ee3d5f
Binary files /dev/null and b/tree-plus.ico differ
diff --git a/tree-plus.svg b/tree-plus.svg
new file mode 100644
index 0000000..4d2eb3f
--- /dev/null
+++ b/tree-plus.svg
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="256px"
+   height="256px"
+   viewBox="0 0 256 256"
+   version="1.1"
+   id="SVGRoot"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="tree-plus.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="3.359375"
+     inkscape:cx="84.390698"
+     inkscape:cy="128"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1680"
+     inkscape:window-height="1027"
+     inkscape:window-x="1912"
+     inkscape:window-y="22"
+     inkscape:window-maximized="1"
+     inkscape:grid-bbox="true"
+     showguides="false"
+     inkscape:object-nodes="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4075" />
+  </sodipodi:namedview>
+  <defs
+     id="defs3756" />
+  <metadata
+     id="metadata3759">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:groupmode="layer"
+     inkscape:label="layer">
+    <g
+       id="box"
+       inkscape:label="box"
+       style="fill:#000000;fill-opacity:1"
+       transform="translate(8,8)">
+      <rect
+         y="56"
+         x="56"
+         height="16"
+         width="144"
+         id="top"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="top" />
+      <rect
+         y="184"
+         x="56"
+         height="16"
+         width="144"
+         id="bottom"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="bottom" />
+      <rect
+         transform="rotate(90)"
+         y="-72"
+         x="56"
+         height="16"
+         width="144"
+         id="left"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="left" />
+      <rect
+         transform="rotate(90)"
+         y="-200"
+         x="56"
+         height="16"
+         width="144"
+         id="right"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="right" />
+    </g>
+    <g
+       id="symbol"
+       inkscape:label="symbol"
+       style="fill:#000000;fill-opacity:1"
+       transform="translate(8,8)">
+      <rect
+         y="88"
+         x="120"
+         height="80"
+         width="16"
+         id="vert"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="vert" />
+      <rect
+         transform="rotate(90)"
+         y="-168"
+         x="120"
+         height="80"
+         width="16"
+         id="horiz"
+         style="fill:#000000;fill-opacity:1"
+         inkscape:label="horiz" />
+    </g>
+  </g>
+</svg>
diff --git a/window.h b/window.h
index ca6baa6..d8b712b 100644
--- a/window.h
+++ b/window.h
@@ -138,6 +138,13 @@ public:
     return false;
   };
 
+  virtual bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
+  {
+    // Not processed by default.  Override in derived classes to
+    // do something with command messages if you need to.
+    return false;
+  };
+
   RECT GetWindowRect() const;
   RECT GetClientRect() const;
 


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2018-10-13 18:13 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-10-13 18:13 [setup - the official Cygwin setup program] branch master, updated. release_2.894 jturney

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).