public inbox for cygwin-apps@cygwin.com
 help / color / mirror / Atom feed
* [PATCH setup 01/13] Change packagemeta::_actions to an enum
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
  2018-08-05 22:09 ` [PATCH setup 02/13] Add OnNotify virtual function to class Window for WM_NOTIFY notifications Jon Turney
  2018-08-05 22:09 ` [PATCH setup 03/13] Drop 'using namespace std;' from PickView.cc Jon Turney
@ 2018-08-05 22:09 ` Jon Turney
  2018-08-05 22:10 ` [PATCH setup 06/13] Add methods for listing possible actions on, and applying one to, a package Jon Turney
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:09 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

---
 PickCategoryLine.cc |  6 +++---
 package_meta.cc     | 35 +++++++----------------------------
 package_meta.h      | 29 +++++++++--------------------
 3 files changed, 19 insertions(+), 51 deletions(-)

diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc
index 24fecb3..e428419 100644
--- a/PickCategoryLine.cc
+++ b/PickCategoryLine.cc
@@ -55,7 +55,8 @@ PickCategoryLine::paint (HDC hdc, HRGN hUpdRgn, int x, int y, int row, int show_
       
       // draw the caption ('Default', 'Install', etc)
       TextOut (hdc, spin_x + SPIN_WIDTH + ICON_MARGIN, r, 
-               current_default.caption (), strlen (current_default.caption ()));
+               packagemeta::action_caption (current_default),
+               strlen (packagemeta::action_caption (current_default)));
       row++;
     }
   if (collapsed)
@@ -110,8 +111,7 @@ PickCategoryLine::click (int const myrow, int const ClickedRow, int const x)
     {
       if ((size_t) x >= spin_x)
 	{
-	  ++current_default;
-	  
+	  current_default = (packagemeta::_actions)((current_default + 1) % 4);
 	  return set_action (current_default);
 	}
       else
diff --git a/package_meta.cc b/package_meta.cc
index f765baf..dffe3ac 100644
--- a/package_meta.cc
+++ b/package_meta.cc
@@ -54,35 +54,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) :
@@ -96,14 +83,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 unary_function<T, void>
 {
   removeCategory(packagemeta *pkg) : _pkg (pkg) {}
diff --git a/package_meta.h b/package_meta.h
index 8db10e2..df66bda 100644
--- a/package_meta.h
+++ b/package_meta.h
@@ -50,26 +50,15 @@ 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;
+  enum _actions
+    {
+     Default_action,
+     Install_action,
+     Reinstall_action,
+     Uninstall_action,
+    };
+  static const char *action_caption (_actions value);
+
   void set_action (trusts const t);
   void set_action (_actions, packageversion const & default_version);
 
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 00/13] ListView Package Chooser
@ 2018-08-05 22:09 Jon Turney
  2018-08-05 22:09 ` [PATCH setup 02/13] Add OnNotify virtual function to class Window for WM_NOTIFY notifications Jon Turney
                   ` (14 more replies)
  0 siblings, 15 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:09 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

Drag setup into the 1990s, by replacing the custom-drawn package chooser 
with a ListView common control.

As well as removing a lot of Win32 GDI drawing, this also enables the 
following improvements to be much more straightforward:

* Use standard UI elements to choose an action to take on a package or 
category, rather than the weird UX of clicking to cycle around a list of 
options of undisclosed length.

* Add tooltips (initially, the ldesc as a tooltip for sdesc)

* Make the package chooser keyboard accessible (not done yet)

The branch for this patch series can be found at:
   https://github.com/jon-turney/cygwin-setup/tree/listview


Jon Turney (13):
  Change packagemeta::_actions to an enum
  Add OnNotify virtual function to class Window for WM_NOTIFY
    notifications
  Drop 'using namespace std;' from PickView.cc
  Use a ListView common control rather than a hand-built grid
  Custom draw checkboxes in ListView control
  Add methods for listing possible actions on, and applying one to, a
    package
  Custom draw popup menus in ListView control
  Show the count of packages in a category
  Use an icon to represent expanded/collapsed state
  Use indents in category view
  Add LDesc() accessor method to SolvableVersion
  Restore packagemeta::LDesc()
  Add ldesc tooltips to sdesc column of listview

 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         | 898 ++++++++------------------------------------
 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_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 +
 32 files changed, 1608 insertions(+), 1313 deletions(-)
 create mode 100644 ActionList.h
 create mode 100644 ListView.cc
 create mode 100644 ListView.h
 delete mode 100644 PickLine.h
 delete mode 100644 check-na.bmp
 delete mode 100644 check-no.bmp
 delete mode 100644 check-yes.bmp
 delete mode 100644 choose-spin.bmp
 delete mode 100644 tree-minus.bmp
 create mode 100644 tree-minus.ico
 create mode 100755 tree-minus.svg
 delete mode 100644 tree-plus.bmp
 create mode 100644 tree-plus.ico
 create mode 100644 tree-plus.svg

-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 03/13] Drop 'using namespace std;' from PickView.cc
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
  2018-08-05 22:09 ` [PATCH setup 02/13] Add OnNotify virtual function to class Window for WM_NOTIFY notifications Jon Turney
@ 2018-08-05 22:09 ` Jon Turney
  2018-08-05 22:09 ` [PATCH setup 01/13] Change packagemeta::_actions to an enum Jon Turney
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:09 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

---
 PickView.cc | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/PickView.cc b/PickView.cc
index c583eea..57e8f85 100644
--- a/PickView.cc
+++ b/PickView.cc
@@ -28,8 +28,6 @@
 #include "LogSingleton.h"
 #include "Exception.h"
 
-using namespace std;
-
 static PickView::Header pkg_headers[] = {
   {"Current", 0, 0, true},
   {"New", 0, 0, true},
@@ -248,10 +246,10 @@ PickView::mode_caption (views mode)
 
 /* meant to be called on packagemeta::categories */
 bool
-isObsolete (set <std::string, casecompare_lt_op> &categories)
+isObsolete (std::set <std::string, casecompare_lt_op> &categories)
 {
-  set <std::string, casecompare_lt_op>::const_iterator i;
-  
+  std::set <std::string, casecompare_lt_op>::const_iterator i;
+
   for (i = categories.begin (); i != categories.end (); ++i)
     if (isObsolete (*i))
       return true;
@@ -295,7 +293,7 @@ PickView::insert_category (Category *cat, bool collapsed)
     return;
   PickCategoryLine & catline = *new PickCategoryLine (*this, *cat, 1, collapsed);
   int packageCount = 0;
-  for (vector <packagemeta *>::iterator i = cat->second.begin ();
+  for (std::vector <packagemeta *>::iterator i = cat->second.begin ();
        i != cat->second.end () ; ++i)
     {
       if (packageFilterString.empty () \
@@ -439,7 +437,7 @@ PickView::init_headers (HDC dc)
       if (pkg.installed)
         note_width (headers, dc, pkg.installed.Canonical_version (),
                     HMARGIN, current_col);
-      for (set<packageversion>::iterator i = pkg.versions.begin ();
+      for (std::set<packageversion>::iterator i = pkg.versions.begin ();
 	   i != pkg.versions.end (); ++i)
 	{
           if (*i != pkg.installed)
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 02/13] Add OnNotify virtual function to class Window for WM_NOTIFY notifications
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
@ 2018-08-05 22:09 ` Jon Turney
  2018-08-05 22:09 ` [PATCH setup 03/13] Drop 'using namespace std;' from PickView.cc Jon Turney
                   ` (13 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:09 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

Add OnNotify virtual function to class Window for WM_NOTIFY notifications
Note that the result is returned via DWLP_MSGRESULT
---
 proppage.cc | 14 +++++++++++++-
 window.h    |  7 +++++++
 2 files changed, 20 insertions(+), 1 deletion(-)

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/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;
 
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 07/13] Custom draw popup menus in ListView control
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (5 preceding siblings ...)
  2018-08-05 22:10 ` [PATCH setup 04/13] Use a ListView common control rather than a hand-built grid Jon Turney
@ 2018-08-05 22:10 ` Jon Turney
  2018-08-05 22:10 ` [PATCH setup 09/13] Use an icon to represent expanded/collapsed state Jon Turney
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

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
---
 ListView.cc         | 105 +++++++++++++++++++++++++++++++++++++++++---
 ListView.h          |   6 ++-
 PickCategoryLine.cc |  20 ++++++++-
 PickCategoryLine.h  |   3 +-
 PickPackageLine.cc  |  16 +++++--
 PickPackageLine.h   |   3 +-
 choose.cc           |   2 +-
 package_meta.cc     |  64 ---------------------------
 package_meta.h      |   1 -
 9 files changed, 139 insertions(+), 81 deletions(-)

diff --git a/ListView.cc b/ListView.cc
index e3f1e44..a555caa 100644
--- a/ListView.cc
+++ b/ListView.cc
@@ -274,18 +274,31 @@ ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
 #endif
       int iRow = pNmItemAct->iItem;
       int iCol = pNmItemAct->iSubItem;
+      if (iRow < 0)
+        return false;
 
-      if (iRow >= 0)
+      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
-          int update = (*contents)[iRow]->do_action(iCol);
+          update = (*contents)[iRow]->do_action(iCol, 0);
+        }
 
-          // Update items, if needed
-          if (update > 0)
-            {
-              ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
-            }
+      // Update items, if needed
+      if (update > 0)
+        {
+          ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
         }
+
       return true;
     }
     break;
@@ -346,6 +359,41 @@ ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
                   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;
@@ -369,3 +417,46 @@ 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
index 3aabc3f..b14777c 100644
--- a/ListView.h
+++ b/ListView.h
@@ -14,6 +14,7 @@
 #ifndef SETUP_LISTVIEW_H
 #define SETUP_LISTVIEW_H
 
+#include "ActionList.h"
 #include "win32.h"
 #include <vector>
 
@@ -28,7 +29,8 @@ class ListViewLine
  public:
   virtual ~ListViewLine() {};
   virtual const std::string get_text(int col) const = 0;
-  virtual int do_action(int col) = 0;
+  virtual ActionList *get_actions(int col) const = 0;
+  virtual int do_action(int col, int id) = 0;
 };
 
 typedef std::vector<ListViewLine *> ListViewContents;
@@ -40,6 +42,7 @@ class ListView
   {
     text,
     checkbox,
+    popup,
   };
 
   class Header
@@ -75,6 +78,7 @@ class ListView
 
   void initColumns(HeaderList hl);
   void empty(void);
+  int popup_menu(int iRow, int iCol, POINT p);
 };
 
 #endif /* SETUP_LISTVIEW_H */
diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc
index 6737454..ec15b4a 100644
--- a/PickCategoryLine.cc
+++ b/PickCategoryLine.cc
@@ -17,6 +17,7 @@
 #include "package_db.h"
 #include "PickView.h"
 #include "window.h"
+#include "package_meta.h"
 
 const std::string
 PickCategoryLine::get_text (int col_num) const
@@ -34,7 +35,7 @@ PickCategoryLine::get_text (int col_num) const
 }
 
 int
-PickCategoryLine::do_action(int col_num)
+PickCategoryLine::do_action(int col_num, int action_id)
 {
   if (col_num == pkgname_col)
     {
@@ -44,9 +45,24 @@ PickCategoryLine::do_action(int col_num)
   else if (col_num == new_col)
     {
       theView.GetParent ()->SetBusy ();
-      int u = cat_tree->do_action((packagemeta::_actions)((cat_tree->action() + 1) % 4), theView.deftrust);
+      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;
+}
diff --git a/PickCategoryLine.h b/PickCategoryLine.h
index 9423eb8..6c18018 100644
--- a/PickCategoryLine.h
+++ b/PickCategoryLine.h
@@ -33,7 +33,8 @@ public:
   }
 
   const std::string get_text(int col) const;
-  int do_action(int col);
+  ActionList *get_actions(int col) const;
+  int do_action(int col, int action_id);
 
 private:
   CategoryTree * cat_tree;
diff --git a/PickPackageLine.cc b/PickPackageLine.cc
index b348ab0..67baad2 100644
--- a/PickPackageLine.cc
+++ b/PickPackageLine.cc
@@ -100,14 +100,13 @@ PickPackageLine::get_text(int col_num) const
 }
 
 int
-PickPackageLine::do_action(int col_num)
+PickPackageLine::do_action(int col_num, int action_id)
 {
   if (col_num == new_col)
     {
-      pkg.set_action (theView.deftrust);
+      pkg.select_action(action_id, theView.deftrust);
       return 1;
     }
-
   if (col_num == bintick_col)
     {
       if (pkg.desired.accessible ())
@@ -133,3 +132,14 @@ PickPackageLine::do_action(int col_num)
 
   return 0;
 }
+
+ActionList *
+PickPackageLine::get_actions(int col_num) const
+{
+  if (col_num == new_col)
+    {
+      return pkg.list_actions (theView.deftrust);
+    }
+
+  return NULL;
+}
diff --git a/PickPackageLine.h b/PickPackageLine.h
index a8c3c0d..7d96d44 100644
--- a/PickPackageLine.h
+++ b/PickPackageLine.h
@@ -30,7 +30,8 @@ public:
   {
   };
   const std::string get_text(int col) const;
-  int do_action(int col);
+  ActionList *get_actions(int col_num) const;
+  int do_action(int col, int action_id);
 private:
   packagemeta & pkg;
   PickView & theView;
diff --git a/choose.cc b/choose.cc
index c65a107..d40e824 100644
--- a/choose.cc
+++ b/choose.cc
@@ -134,7 +134,7 @@ ChooserPage::~ChooserPage ()
 static ListView::Header pkg_headers[] = {
   {"Package",     LVCFMT_LEFT,  ListView::ControlType::text},
   {"Current",     LVCFMT_LEFT,  ListView::ControlType::text},
-  {"New",         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},
diff --git a/package_meta.cc b/package_meta.cc
index f55c3f3..4f7d39a 100644
--- a/package_meta.cc
+++ b/package_meta.cc
@@ -423,70 +423,6 @@ packagemeta::action_caption () const
     return desired.Canonical_version ();
 }
 
-/* Set the next action given a current action.  */
-void
-packagemeta::set_action (trusts const trust)
-{
-  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 ())
-    {
-      pick (true);
-      return;
-    }
-
-  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);
-    }
-  else
-    {
-      desired = packageversion ();
-      pick(false);
-      srcpick(false);
-    }
-
-  /* Memorize the fact that the user picked at least once. */
-  if (!installed)
-    user_picked = true;
-}
-
 void
 packagemeta::select_action (int id, trusts const deftrust)
 {
diff --git a/package_meta.h b/package_meta.h
index 0f01837..8a42319 100644
--- a/package_meta.h
+++ b/package_meta.h
@@ -60,7 +60,6 @@ public:
     };
   static const char *action_caption (_actions value);
 
-  void set_action (trusts const t);
   void set_action (_actions, packageversion const & default_version);
   ActionList *list_actions(trusts const trust);
   void select_action (int id, trusts const deftrust);
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 08/13] Show the count of packages in a category
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (7 preceding siblings ...)
  2018-08-05 22:10 ` [PATCH setup 09/13] Use an icon to represent expanded/collapsed state Jon Turney
@ 2018-08-05 22:10 ` Jon Turney
  2018-08-05 22:11 ` [PATCH setup 10/13] Use indents in category view Jon Turney
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

---
 PickCategoryLine.cc | 8 ++++++--
 PickCategoryLine.h  | 4 +++-
 PickView.cc         | 4 ++--
 3 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc
index ec15b4a..736e3c6 100644
--- a/PickCategoryLine.cc
+++ b/PickCategoryLine.cc
@@ -18,14 +18,18 @@
 #include "PickView.h"
 #include "window.h"
 #include "package_meta.h"
+#include <sstream>
 
 const std::string
 PickCategoryLine::get_text (int col_num) const
 {
   if (col_num == pkgname_col)
     {
-      std::string s = (cat_tree->collapsed() ? "[+] " : "[-] ") + cat_tree->category().first;
-      return s;
+      std::ostringstream s;
+      s << (cat_tree->collapsed() ? "[+] " : "[-] ") << cat_tree->category().first;
+      if (pkgcount)
+        s << " (" << pkgcount << ")";
+      return s.str();
     }
   else if (col_num == new_col)
     {
diff --git a/PickCategoryLine.h b/PickCategoryLine.h
index 6c18018..ec160f9 100644
--- a/PickCategoryLine.h
+++ b/PickCategoryLine.h
@@ -23,8 +23,9 @@
 class PickCategoryLine: public ListViewLine
 {
 public:
-  PickCategoryLine (PickView & aView, CategoryTree * _tree) :
+  PickCategoryLine (PickView & aView, CategoryTree * _tree, int _pkgcount) :
     cat_tree (_tree),
+    pkgcount(_pkgcount),
     theView (aView)
   {
   };
@@ -38,6 +39,7 @@ public:
 
 private:
   CategoryTree * cat_tree;
+  int pkgcount;
   PickView & theView;
 };
 
diff --git a/PickView.cc b/PickView.cc
index 967d53b..6e1af0c 100644
--- a/PickView.cc
+++ b/PickView.cc
@@ -169,12 +169,12 @@ PickView::insert_category (CategoryTree *cat_tree)
     return;
 
   // if it's not the "All" category
+  int packageCount = 0;
   bool hasContents = false;
   bool isAll = casecompare(cat->first, "All") == 0;
   if (!isAll)
     {
       // count the number of packages in this category
-      int packageCount = 0;
       for (std::vector <packagemeta *>::const_iterator i = cat->second.begin ();
            i != cat->second.end () ; ++i)
         {
@@ -197,7 +197,7 @@ PickView::insert_category (CategoryTree *cat_tree)
     return;
 
   // insert line for the category
-  contents.push_back(new PickCategoryLine(*this, cat_tree));
+  contents.push_back(new PickCategoryLine(*this, cat_tree, packageCount));
 
   // if not collapsed
   if (!cat_tree->collapsed())
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 04/13] Use a ListView common control rather than a hand-built grid
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (4 preceding siblings ...)
  2018-08-05 22:10 ` [PATCH setup 05/13] Custom draw checkboxes in ListView control Jon Turney
@ 2018-08-05 22:10 ` Jon Turney
  2018-08-05 22:10 ` [PATCH setup 07/13] Custom draw popup menus in ListView control Jon Turney
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 05/13] Custom draw checkboxes in ListView control
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (3 preceding siblings ...)
  2018-08-05 22:10 ` [PATCH setup 06/13] Add methods for listing possible actions on, and applying one to, a package Jon Turney
@ 2018-08-05 22:10 ` Jon Turney
  2018-08-05 22:10 ` [PATCH setup 04/13] Use a ListView common control rather than a hand-built grid Jon Turney
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

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
---
 ListView.cc | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 ListView.h  |  7 ++++++
 choose.cc   | 16 +++++++-------
 3 files changed, 78 insertions(+), 8 deletions(-)

diff --git a/ListView.cc b/ListView.cc
index 97ee44c..e3f1e44 100644
--- a/ListView.cc
+++ b/ListView.cc
@@ -289,6 +289,69 @@ ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
       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;
+              }
+            *pResult = result;
+            return true;
+          }
+        }
+    }
   }
 
   // We don't care.
diff --git a/ListView.h b/ListView.h
index d339011..3aabc3f 100644
--- a/ListView.h
+++ b/ListView.h
@@ -36,11 +36,18 @@ typedef std::vector<ListViewLine *> ListViewContents;
 class ListView
 {
  public:
+  enum class ControlType
+  {
+    text,
+    checkbox,
+  };
+
   class Header
   {
   public:
     const char *text;
     int fmt;
+    ControlType type;
     int width;
     int hdr_width;
   };
diff --git a/choose.cc b/choose.cc
index c86294a..c65a107 100644
--- a/choose.cc
+++ b/choose.cc
@@ -132,14 +132,14 @@ ChooserPage::~ChooserPage ()
 }
 
 static ListView::Header pkg_headers[] = {
-  {"Package",     LVCFMT_LEFT},
-  {"Current",     LVCFMT_LEFT},
-  {"New",         LVCFMT_LEFT},
-  {"Bin?",        LVCFMT_LEFT},
-  {"Src?",        LVCFMT_LEFT},
-  {"Categories",  LVCFMT_LEFT},
-  {"Size",        LVCFMT_RIGHT},
-  {"Description", LVCFMT_LEFT},
+  {"Package",     LVCFMT_LEFT,  ListView::ControlType::text},
+  {"Current",     LVCFMT_LEFT,  ListView::ControlType::text},
+  {"New",         LVCFMT_LEFT,  ListView::ControlType::text},
+  {"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}
 };
 
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 09/13] Use an icon to represent expanded/collapsed state
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (6 preceding siblings ...)
  2018-08-05 22:10 ` [PATCH setup 07/13] Custom draw popup menus in ListView control Jon Turney
@ 2018-08-05 22:10 ` Jon Turney
  2018-08-05 22:10 ` [PATCH setup 08/13] Show the count of packages in a category Jon Turney
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

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'
---
 ListView.cc         |  18 ++++++-
 ListView.h          |   3 ++
 Makefile.am         |   4 +-
 PickCategoryLine.cc |   8 ++-
 PickCategoryLine.h  |   1 +
 PickPackageLine.h   |   1 +
 res.rc              |   2 +
 resource.h          |   2 +
 tree-minus.ico      | Bin 0 -> 299654 bytes
 tree-minus.svg      | 118 +++++++++++++++++++++++++++++++++++++++++
 tree-plus.ico       | Bin 0 -> 299671 bytes
 tree-plus.svg       | 126 ++++++++++++++++++++++++++++++++++++++++++++
 12 files changed, 280 insertions(+), 3 deletions(-)
 create mode 100644 tree-minus.ico
 create mode 100755 tree-minus.svg
 create mode 100644 tree-plus.ico
 create mode 100644 tree-plus.svg

diff --git a/ListView.cc b/ListView.cc
index a555caa..0c451d1 100644
--- a/ListView.cc
+++ b/ListView.cc
@@ -13,6 +13,7 @@
 
 #include "ListView.h"
 #include "LogSingleton.h"
+#include "resource.h"
 
 #include <commctrl.h>
 
@@ -54,6 +55,15 @@ ListView::init(HWND parent, int id, HeaderList headers)
 
   // populate with columns
   initColumns(headers);
+
+  // create a small icon imagelist and assign to listview control
+  // (the order of images matches ListViewLine::State enum)
+  HIMAGELIST 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)));
+  ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL);
 }
 
 void
@@ -179,10 +189,11 @@ ListView::setContents(ListViewContents *_contents)
   for (i = 0; i < contents->size();  i++)
     {
       LVITEM lvi;
-      lvi.mask = LVIF_TEXT;
+      lvi.mask = LVIF_TEXT | LVIF_IMAGE;
       lvi.iItem = i;
       lvi.iSubItem = 0;
       lvi.pszText = LPSTR_TEXTCALLBACK;
+      lvi.iImage = I_IMAGECALLBACK;
 
       ListView_InsertItem(hWndListView, &lvi);
     }
@@ -247,6 +258,11 @@ ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
           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;
diff --git a/ListView.h b/ListView.h
index b14777c..f5aa1d9 100644
--- a/ListView.h
+++ b/ListView.h
@@ -27,8 +27,11 @@
 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 ActionList *get_actions(int col) const = 0;
   virtual int do_action(int col, int id) = 0;
 };
diff --git a/Makefile.am b/Makefile.am
index b58c9b7..7c1f993 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -46,7 +46,9 @@ EXTRA_DIST = \
 	cygwin-setup.ico \
 	cygwin-terminal.ico \
 	setup.exe.manifest \
-	setup64.exe.manifest
+	setup64.exe.manifest \
+	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
diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc
index 736e3c6..21795b2 100644
--- a/PickCategoryLine.cc
+++ b/PickCategoryLine.cc
@@ -26,7 +26,7 @@ PickCategoryLine::get_text (int col_num) const
   if (col_num == pkgname_col)
     {
       std::ostringstream s;
-      s << (cat_tree->collapsed() ? "[+] " : "[-] ") << cat_tree->category().first;
+      s << cat_tree->category().first;
       if (pkgcount)
         s << " (" << pkgcount << ")";
       return s.str();
@@ -70,3 +70,9 @@ PickCategoryLine::get_actions(int col) const
 
   return al;
 }
+
+ListViewLine::State
+PickCategoryLine::get_state() const
+{
+  return cat_tree->collapsed() ? State::collapsed : State::expanded;
+}
diff --git a/PickCategoryLine.h b/PickCategoryLine.h
index ec160f9..6b54c3f 100644
--- a/PickCategoryLine.h
+++ b/PickCategoryLine.h
@@ -34,6 +34,7 @@ public:
   }
 
   const std::string get_text(int col) const;
+  State get_state() const;
   ActionList *get_actions(int col) const;
   int do_action(int col, int action_id);
 
diff --git a/PickPackageLine.h b/PickPackageLine.h
index 7d96d44..dacad28 100644
--- a/PickPackageLine.h
+++ b/PickPackageLine.h
@@ -30,6 +30,7 @@ public:
   {
   };
   const std::string get_text(int col) const;
+  State get_state() const { return State::nothing; }
   ActionList *get_actions(int col_num) const;
   int do_action(int col, int action_id);
 private:
diff --git a/res.rc b/res.rc
index 27f0378..10f20ba 100644
--- a/res.rc
+++ b/res.rc
@@ -519,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"
 
 /////////////////////////////////////////////////////////////////////////////
 //
diff --git a/resource.h b/resource.h
index 2f1036b..852bdc0 100644
--- a/resource.h
+++ b/resource.h
@@ -76,6 +76,8 @@
 #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
 
diff --git a/tree-minus.ico b/tree-minus.ico
new file mode 100644
index 0000000000000000000000000000000000000000..46fd3b117b0fcaffc9c12331e4ccdd1fed9f61e1
GIT binary patch
literal 299654
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
IZR_j*0p@PTA^-pY

literal 0
HcmV?d00001

diff --git a/tree-minus.svg b/tree-minus.svg
new file mode 100755
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.ico b/tree-plus.ico
new file mode 100644
index 0000000000000000000000000000000000000000..8ee3d5fed295f1b79639845c67a86c5145cfa5c8
GIT binary patch
literal 299671
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

literal 0
HcmV?d00001

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>
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 06/13] Add methods for listing possible actions on, and applying one to, a package
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (2 preceding siblings ...)
  2018-08-05 22:09 ` [PATCH setup 01/13] Change packagemeta::_actions to an enum Jon Turney
@ 2018-08-05 22:10 ` Jon Turney
  2018-08-05 22:10 ` [PATCH setup 05/13] Custom draw checkboxes in ListView control Jon Turney
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:10 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

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
---
 ActionList.h    | 55 +++++++++++++++++++++++++++++++++++++++
 Makefile.am     |  1 +
 PickView.h      |  2 +-
 package_meta.cc | 69 +++++++++++++++++++++++++++++++++++++++++++++++++
 package_meta.h  |  5 +++-
 5 files changed, 130 insertions(+), 2 deletions(-)
 create mode 100644 ActionList.h

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/Makefile.am b/Makefile.am
index bce4c8c..b58c9b7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -95,6 +95,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 \
diff --git a/PickView.h b/PickView.h
index 9777d15..fc9216e 100644
--- a/PickView.h
+++ b/PickView.h
@@ -152,7 +152,7 @@ public:
              pkg != _cat.second.end();
              ++pkg)
           {
-            (*pkg)->set_action(action_id, (*pkg)->trustp(true, deftrust));
+            (*pkg)->select_action(action_id, deftrust);
             l++;
           }
 
diff --git a/package_meta.cc b/package_meta.cc
index dffe3ac..f55c3f3 100644
--- a/package_meta.cc
+++ b/package_meta.cc
@@ -487,6 +487,75 @@ packagemeta::set_action (trusts const trust)
     user_picked = true;
 }
 
+void
+packagemeta::select_action (int id, trusts const deftrust)
+{
+  if (id <= 0)
+    {
+      // Install a specific version
+      set<packageversion>::iterator i = versions.begin ();
+      for (int j = -id; j > 0; j--)
+        i++;
+
+      set_action(Install_action, *i);
+    }
+  else
+    {
+      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. */
+  if (!installed)
+    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);
+
+  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 df66bda..0f01837 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;
 
@@ -52,7 +53,7 @@ public:
 
   enum _actions
     {
-     Default_action,
+     Default_action = 1,
      Install_action,
      Reinstall_action,
      Uninstall_action,
@@ -61,6 +62,8 @@ public:
 
   void set_action (trusts const t);
   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)
   {
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 10/13] Use indents in category view
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (8 preceding siblings ...)
  2018-08-05 22:10 ` [PATCH setup 08/13] Show the count of packages in a category Jon Turney
@ 2018-08-05 22:11 ` Jon Turney
  2018-08-05 22:12 ` [PATCH setup 11/13] Add LDesc() accessor method to SolvableVersion Jon Turney
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:11 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

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...)
---
 ListView.cc         | 29 +++++++++++++++++++++--------
 ListView.h          |  7 ++++++-
 PickCategoryLine.cc |  6 ++++++
 PickCategoryLine.h  |  7 +++++--
 PickPackageLine.cc  |  6 ++++++
 PickPackageLine.h   |  7 +++++--
 PickView.cc         | 10 +++++-----
 PickView.h          |  2 +-
 8 files changed, 55 insertions(+), 19 deletions(-)

diff --git a/ListView.cc b/ListView.cc
index 0c451d1..b0351cd 100644
--- a/ListView.cc
+++ b/ListView.cc
@@ -56,14 +56,17 @@ ListView::init(HWND parent, int id, HeaderList headers)
   // populate with columns
   initColumns(headers);
 
-  // create a small icon imagelist and assign to listview control
+  // create a small icon imagelist
   // (the order of images matches ListViewLine::State enum)
-  HIMAGELIST hImgList = ImageList_Create(GetSystemMetrics(SM_CXSMICON),
-                                         GetSystemMetrics(SM_CYSMICON),
-                                         ILC_COLOR32, 2, 0);
+  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)));
-  ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL);
+
+  // create an empty imagelist, used to reset the indent
+  hEmptyImgList = ImageList_Create(1, 1,
+                                   ILC_COLOR32, 2, 0);
 }
 
 void
@@ -172,7 +175,7 @@ ListView::resizeColumns(void)
 }
 
 void
-ListView::setContents(ListViewContents *_contents)
+ListView::setContents(ListViewContents *_contents, bool tree)
 {
   contents = _contents;
 
@@ -185,15 +188,25 @@ ListView::setContents(ListViewContents *_contents)
 
   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 | LVIF_IMAGE;
+      lvi.mask = LVIF_TEXT | (tree ? LVIF_IMAGE | LVIF_INDENT : 0);
       lvi.iItem = i;
       lvi.iSubItem = 0;
       lvi.pszText = LPSTR_TEXTCALLBACK;
-      lvi.iImage = I_IMAGECALLBACK;
+      if (tree)
+        {
+          lvi.iImage = I_IMAGECALLBACK;
+          lvi.iIndent = (*contents)[i]->get_indent();
+        }
 
       ListView_InsertItem(hWndListView, &lvi);
     }
diff --git a/ListView.h b/ListView.h
index f5aa1d9..34d6267 100644
--- a/ListView.h
+++ b/ListView.h
@@ -16,6 +16,7 @@
 
 #include "ActionList.h"
 #include "win32.h"
+#include <commctrl.h>
 #include <vector>
 
 // ---------------------------------------------------------------------------
@@ -32,6 +33,7 @@ class ListViewLine
   virtual ~ListViewLine() {};
   virtual const std::string get_text(int col) const = 0;
   virtual State get_state() 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;
 };
@@ -66,7 +68,7 @@ class ListView
   void noteColumnWidthEnd();
   void resizeColumns(void);
 
-  void setContents(ListViewContents *contents);
+  void setContents(ListViewContents *contents, bool tree = false);
   void setEmptyText(const char *text);
 
   bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult);
@@ -75,6 +77,9 @@ class ListView
   HWND hWndParent;
   HWND hWndListView;
   HDC dc;
+  HIMAGELIST hImgList;
+  HIMAGELIST hEmptyImgList;
+
   ListViewContents *contents;
   HeaderList headers;
   const char *empty_list_text;
diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc
index 21795b2..e206aee 100644
--- a/PickCategoryLine.cc
+++ b/PickCategoryLine.cc
@@ -76,3 +76,9 @@ PickCategoryLine::get_state() const
 {
   return cat_tree->collapsed() ? State::collapsed : State::expanded;
 }
+
+int
+PickCategoryLine::get_indent() const
+{
+  return indent;
+}
diff --git a/PickCategoryLine.h b/PickCategoryLine.h
index 6b54c3f..a5daa8c 100644
--- a/PickCategoryLine.h
+++ b/PickCategoryLine.h
@@ -23,10 +23,11 @@
 class PickCategoryLine: public ListViewLine
 {
 public:
-  PickCategoryLine (PickView & aView, CategoryTree * _tree, int _pkgcount) :
+  PickCategoryLine (PickView & aView, CategoryTree * _tree, int _pkgcount, int _indent) :
     cat_tree (_tree),
     pkgcount(_pkgcount),
-    theView (aView)
+    theView (aView),
+    indent(_indent)
   {
   };
   ~PickCategoryLine ()
@@ -35,6 +36,7 @@ public:
 
   const std::string get_text(int col) const;
   State get_state() const;
+  int get_indent() const;
   ActionList *get_actions(int col) const;
   int do_action(int col, int action_id);
 
@@ -42,6 +44,7 @@ private:
   CategoryTree * cat_tree;
   int pkgcount;
   PickView & theView;
+  int indent;
 };
 
 #endif /* SETUP_PICKCATEGORYLINE_H */
diff --git a/PickPackageLine.cc b/PickPackageLine.cc
index 67baad2..a602c90 100644
--- a/PickPackageLine.cc
+++ b/PickPackageLine.cc
@@ -143,3 +143,9 @@ PickPackageLine::get_actions(int col_num) const
 
   return NULL;
 }
+
+int
+PickPackageLine::get_indent() const
+{
+  return indent;
+}
diff --git a/PickPackageLine.h b/PickPackageLine.h
index dacad28..53c389b 100644
--- a/PickPackageLine.h
+++ b/PickPackageLine.h
@@ -24,18 +24,21 @@ class PickView;
 class PickPackageLine: public ListViewLine
 {
 public:
-  PickPackageLine (PickView &aView, packagemeta & apkg) :
+  PickPackageLine (PickView &aView, packagemeta & apkg, int aindent) :
     pkg (apkg),
-    theView (aView)
+    theView (aView),
+    indent (aindent)
   {
   };
   const std::string get_text(int col) const;
   State get_state() const { return State::nothing; }
+  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 6e1af0c..c7ff054 100644
--- a/PickView.cc
+++ b/PickView.cc
@@ -87,7 +87,7 @@ PickView::setViewMode (views mode)
         }
     }
 
-  listview->setContents(&contents);
+  listview->setContents(&contents, view_mode == PickView::views::Category);
 }
 
 PickView::views
@@ -148,12 +148,12 @@ PickView::setObsolete (bool doit)
 }
 
 void
-PickView::insert_pkg (packagemeta & pkg)
+PickView::insert_pkg (packagemeta & pkg, int indent)
 {
   if (!showObsolete && isObsolete (pkg.categories))
     return;
 
-  contents.push_back(new PickPackageLine(*this, pkg));
+  contents.push_back(new PickPackageLine(*this, pkg, indent));
 }
 
 void
@@ -197,7 +197,7 @@ PickView::insert_category (CategoryTree *cat_tree)
     return;
 
   // insert line for the category
-  contents.push_back(new PickCategoryLine(*this, cat_tree, packageCount));
+  contents.push_back(new PickCategoryLine(*this, cat_tree, packageCount, isAll ? 0 : 1));
 
   // if not collapsed
   if (!cat_tree->collapsed())
@@ -212,7 +212,7 @@ PickView::insert_category (CategoryTree *cat_tree)
                   || (*i
                       && StrStrI ((*i)->name.c_str (), packageFilterString.c_str ())))
                 {
-                  insert_pkg(**i);
+                  insert_pkg(**i, 2);
                 }
             }
         }
diff --git a/PickView.h b/PickView.h
index fc9216e..3a6c602 100644
--- a/PickView.h
+++ b/PickView.h
@@ -67,7 +67,7 @@ private:
   CategoryTree *cat_tree_root;
   Window *parent;
 
-  void insert_pkg (packagemeta &);
+  void insert_pkg (packagemeta &, int indent = 0);
   void insert_category (CategoryTree *);
 };
 
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 11/13] Add LDesc() accessor method to SolvableVersion
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (9 preceding siblings ...)
  2018-08-05 22:11 ` [PATCH setup 10/13] Use indents in category view Jon Turney
@ 2018-08-05 22:12 ` Jon Turney
  2018-08-05 22:12 ` [PATCH setup 12/13] Restore packagemeta::LDesc() Jon Turney
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:12 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

---
 libsolv.cc | 14 ++++++++++++++
 libsolv.h  |  1 +
 2 files changed, 15 insertions(+)

diff --git a/libsolv.cc b/libsolv.cc
index 955a1b2..ba54fc5 100644
--- a/libsolv.cc
+++ b/libsolv.cc
@@ -192,6 +192,20 @@ SolvableVersion::SDesc () const
   return sdesc;
 }
 
+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
 {
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
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 13/13] Add ldesc tooltips to sdesc column of listview
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (11 preceding siblings ...)
  2018-08-05 22:12 ` [PATCH setup 12/13] Restore packagemeta::LDesc() Jon Turney
@ 2018-08-05 22:12 ` Jon Turney
  2018-08-06 14:15 ` [PATCH setup 00/13] ListView Package Chooser Ken Brown
  2018-08-06 16:40 ` Achim Gratz
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:12 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

Listview's built-in tooltip support doesn't support subitems, so we have to
build our own

v2: Use string cache
---
 ListView.cc         | 94 +++++++++++++++++++++++++++++++++++++++++++++
 ListView.h          |  4 ++
 PickCategoryLine.cc |  6 +++
 PickCategoryLine.h  |  1 +
 PickPackageLine.cc  | 11 ++++++
 PickPackageLine.h   |  1 +
 6 files changed, 117 insertions(+)

diff --git a/ListView.cc b/ListView.cc
index b0351cd..e287270 100644
--- a/ListView.cc
+++ b/ListView.cc
@@ -67,6 +67,34 @@ ListView::init(HWND parent, int id, HeaderList headers)
   // 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
@@ -429,6 +457,72 @@ ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
           }
         }
     }
+    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.
diff --git a/ListView.h b/ListView.h
index 34d6267..b4fd1fd 100644
--- a/ListView.h
+++ b/ListView.h
@@ -33,6 +33,7 @@ class ListViewLine
   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;
@@ -76,6 +77,7 @@ class ListView
  private:
   HWND hWndParent;
   HWND hWndListView;
+  HWND hWndTip;
   HDC dc;
   HIMAGELIST hImgList;
   HIMAGELIST hEmptyImgList;
@@ -83,6 +85,8 @@ class ListView
   ListViewContents *contents;
   HeaderList headers;
   const char *empty_list_text;
+  int iRow_track;
+  int iCol_track;
 
   void initColumns(HeaderList hl);
   void empty(void);
diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc
index e206aee..1dbecf2 100644
--- a/PickCategoryLine.cc
+++ b/PickCategoryLine.cc
@@ -82,3 +82,9 @@ PickCategoryLine::get_indent() const
 {
   return indent;
 }
+
+const std::string
+PickCategoryLine::get_tooltip(int col_num) const
+{
+  return "";
+}
diff --git a/PickCategoryLine.h b/PickCategoryLine.h
index a5daa8c..9c5e996 100644
--- a/PickCategoryLine.h
+++ b/PickCategoryLine.h
@@ -36,6 +36,7 @@ public:
 
   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);
diff --git a/PickPackageLine.cc b/PickPackageLine.cc
index a602c90..133720b 100644
--- a/PickPackageLine.cc
+++ b/PickPackageLine.cc
@@ -99,6 +99,17 @@ PickPackageLine::get_text(int col_num) const
   return "unknown";
 }
 
+const std::string
+PickPackageLine::get_tooltip(int col_num) const
+{
+  if (col_num == pkg_col)
+    {
+      return pkg.LDesc();
+    }
+
+  return "";
+}
+
 int
 PickPackageLine::do_action(int col_num, int action_id)
 {
diff --git a/PickPackageLine.h b/PickPackageLine.h
index 53c389b..12c7636 100644
--- a/PickPackageLine.h
+++ b/PickPackageLine.h
@@ -32,6 +32,7 @@ public:
   };
   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);
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH setup 12/13] Restore packagemeta::LDesc()
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (10 preceding siblings ...)
  2018-08-05 22:12 ` [PATCH setup 11/13] Add LDesc() accessor method to SolvableVersion Jon Turney
@ 2018-08-05 22:12 ` Jon Turney
  2018-08-05 22:12 ` [PATCH setup 13/13] Add ldesc tooltips to sdesc column of listview Jon Turney
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-08-05 22:12 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

---
 package_meta.cc | 15 +++++++++++++++
 package_meta.h  |  5 +++--
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/package_meta.cc b/package_meta.cc
index 4f7d39a..85aaaf9 100644
--- a/package_meta.cc
+++ b/package_meta.cc
@@ -404,6 +404,21 @@ packagemeta::SDesc () const
   return std::string();
 }
 
+static bool
+hasLDesc(packageversion const &pkg)
+{
+  return pkg.LDesc().size();
+}
+
+const std::string
+packagemeta::LDesc () const
+{
+  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
diff --git a/package_meta.h b/package_meta.h
index 8a42319..0eff8d0 100644
--- a/package_meta.h
+++ b/package_meta.h
@@ -108,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.
    */
-- 
2.17.0

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH setup 00/13] ListView Package Chooser
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (12 preceding siblings ...)
  2018-08-05 22:12 ` [PATCH setup 13/13] Add ldesc tooltips to sdesc column of listview Jon Turney
@ 2018-08-06 14:15 ` Ken Brown
  2018-08-06 16:41   ` Achim Gratz
  2018-08-06 19:19   ` Ken Brown
  2018-08-06 16:40 ` Achim Gratz
  14 siblings, 2 replies; 20+ messages in thread
From: Ken Brown @ 2018-08-06 14:15 UTC (permalink / raw)
  To: cygwin-apps

[-- Attachment #1: Type: text/plain, Size: 948 bytes --]

On 8/5/2018 6:08 PM, Jon Turney wrote:
> Drag setup into the 1990s, by replacing the custom-drawn package chooser
> with a ListView common control.
> 
> As well as removing a lot of Win32 GDI drawing, this also enables the
> following improvements to be much more straightforward:
> 
> * Use standard UI elements to choose an action to take on a package or
> category, rather than the weird UX of clicking to cycle around a list of
> options of undisclosed length.

This is a huge improvement over the existing UX.

> * Add tooltips (initially, the ldesc as a tooltip for sdesc)

This is also a big improvement.  It's nice to see ldesc finally being used.

I have one minor fix (patch attached) and one suggestion: It would be 
good, if possible, for setup to remember the column widths if the user 
changes them, just as it now remembers the size and position of the 
chooser window.

Thanks for doing this.  It must have been a lot of work.

Ken

[-- Attachment #2: 0001-Ensure-that-an-installed-packageversion-has-an-ldesc.patch --]
[-- Type: text/plain, Size: 960 bytes --]

From bd16602c08e6edba7ef7f91b149a8e9a0fe45587 Mon Sep 17 00:00:00 2001
From: Ken Brown <kbrown@cornell.edu>
Date: Mon, 6 Aug 2018 10:05:12 -0400
Subject: [PATCH] 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.
---
 package_db.cc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/package_db.cc b/package_db.cc
index b74aafd..9aa3b8e 100644
--- a/package_db.cc
+++ b/package_db.cc
@@ -149,6 +149,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();
-- 
2.17.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH setup 00/13] ListView Package Chooser
  2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
                   ` (13 preceding siblings ...)
  2018-08-06 14:15 ` [PATCH setup 00/13] ListView Package Chooser Ken Brown
@ 2018-08-06 16:40 ` Achim Gratz
  14 siblings, 0 replies; 20+ messages in thread
From: Achim Gratz @ 2018-08-06 16:40 UTC (permalink / raw)
  To: cygwin-apps

Jon Turney writes:
> Drag setup into the 1990s, by replacing the custom-drawn package chooser 
> with a ListView common control.

Oh, like the new millenium is already knocking on the door?  How about
the Y2K issues?
;-)

Joking aside, I won't be able to test this until at least the next
weekend, but that's all-around good news to hear.  Thank you!

>  create mode 100755 tree-minus.svg

Why not mode 644?


Regards,
Achim.
-- 
+<[Q+ Matrix-12 WAVE#46+305 Neuron microQkb Andromeda XTk Blofeld]>+

SD adaptation for Waldorf rackAttack V1.04R1:
http://Synth.Stromeko.net/Downloads.html#WaldorfSDada

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH setup 00/13] ListView Package Chooser
  2018-08-06 14:15 ` [PATCH setup 00/13] ListView Package Chooser Ken Brown
@ 2018-08-06 16:41   ` Achim Gratz
  2018-08-06 16:47     ` Achim Gratz
  2018-08-06 19:19   ` Ken Brown
  1 sibling, 1 reply; 20+ messages in thread
From: Achim Gratz @ 2018-08-06 16:41 UTC (permalink / raw)
  To: cygwin-apps

Ken Brown writes:
> I have one minor fix (patch attached) and one suggestion: It would be
> good, if possible, for setup to remember the column widths if the user
> changes them, just as it now remembers the size and position of the
> chooser window.

The attached patch seems to do something else entirely.


Regards,
Achim.
-- 
+<[Q+ Matrix-12 WAVE#46+305 Neuron microQkb Andromeda XTk Blofeld]>+

SD adaptations for Waldorf Q V3.00R3 and Q+ V3.54R2:
http://Synth.Stromeko.net/Downloads.html#WaldorfSDada

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH setup 00/13] ListView Package Chooser
  2018-08-06 16:41   ` Achim Gratz
@ 2018-08-06 16:47     ` Achim Gratz
  0 siblings, 0 replies; 20+ messages in thread
From: Achim Gratz @ 2018-08-06 16:47 UTC (permalink / raw)
  To: cygwin-apps

Achim Gratz writes:
> Ken Brown writes:
>> I have one minor fix (patch attached) and one suggestion: It would be
>> good, if possible, for setup to remember the column widths if the user
>> changes them, just as it now remembers the size and position of the
>> chooser window.
>
> The attached patch seems to do something else entirely.

Apparently I can't read, please disregard that comment.


Regards,
Achim.
-- 
+<[Q+ Matrix-12 WAVE#46+305 Neuron microQkb Andromeda XTk Blofeld]>+

DIY Stuff:
http://Synth.Stromeko.net/DIY.html

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH setup 00/13] ListView Package Chooser
  2018-08-06 14:15 ` [PATCH setup 00/13] ListView Package Chooser Ken Brown
  2018-08-06 16:41   ` Achim Gratz
@ 2018-08-06 19:19   ` Ken Brown
  2018-10-13 18:46     ` Jon Turney
  1 sibling, 1 reply; 20+ messages in thread
From: Ken Brown @ 2018-08-06 19:19 UTC (permalink / raw)
  To: cygwin-apps

[-- Attachment #1: Type: text/plain, Size: 114 bytes --]

On 8/6/2018 10:15 AM, Ken Brown wrote:
> I have one minor fix (patch attached)

Slightly revised patch attached.


[-- Attachment #2: 0001-Ensure-that-an-installed-packageversion-has-an-ldesc.patch --]
[-- Type: text/plain, Size: 1486 bytes --]

From 7a166b04523b0d3e4547e937cad429d31ef55e90 Mon Sep 17 00:00:00 2001
From: Ken Brown <kbrown@cornell.edu>
Date: Mon, 6 Aug 2018 10:05:12 -0400
Subject: [PATCH] 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.
---
 package_db.cc | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/package_db.cc b/package_db.cc
index b74aafd..03874ec 100644
--- a/package_db.cc
+++ b/package_db.cc
@@ -149,6 +149,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();
@@ -172,6 +173,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;
-- 
2.17.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH setup 00/13] ListView Package Chooser
  2018-08-06 19:19   ` Ken Brown
@ 2018-10-13 18:46     ` Jon Turney
  0 siblings, 0 replies; 20+ messages in thread
From: Jon Turney @ 2018-10-13 18:46 UTC (permalink / raw)
  To: cygwin-apps

On 06/08/2018 20:19, Ken Brown wrote:
> On 8/6/2018 10:15 AM, Ken Brown wrote:
>> I have one minor fix (patch attached)
> 
> Slightly revised patch attached.

Thanks. I added that patch.

On 06/08/2018 17:39, Achim Gratz wrote:
 > Jon Turney writes:
 >
 >>   create mode 100755 tree-minus.svg
 >
 > Why not mode 644?

My mistake. Fixed.

I'll make a build with this added, so people have a chance to whine^H 
evaluate this change, without having to build it themselves.

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2018-10-13 18:46 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-05 22:09 [PATCH setup 00/13] ListView Package Chooser Jon Turney
2018-08-05 22:09 ` [PATCH setup 02/13] Add OnNotify virtual function to class Window for WM_NOTIFY notifications Jon Turney
2018-08-05 22:09 ` [PATCH setup 03/13] Drop 'using namespace std;' from PickView.cc Jon Turney
2018-08-05 22:09 ` [PATCH setup 01/13] Change packagemeta::_actions to an enum Jon Turney
2018-08-05 22:10 ` [PATCH setup 06/13] Add methods for listing possible actions on, and applying one to, a package Jon Turney
2018-08-05 22:10 ` [PATCH setup 05/13] Custom draw checkboxes in ListView control Jon Turney
2018-08-05 22:10 ` [PATCH setup 04/13] Use a ListView common control rather than a hand-built grid Jon Turney
2018-08-05 22:10 ` [PATCH setup 07/13] Custom draw popup menus in ListView control Jon Turney
2018-08-05 22:10 ` [PATCH setup 09/13] Use an icon to represent expanded/collapsed state Jon Turney
2018-08-05 22:10 ` [PATCH setup 08/13] Show the count of packages in a category Jon Turney
2018-08-05 22:11 ` [PATCH setup 10/13] Use indents in category view Jon Turney
2018-08-05 22:12 ` [PATCH setup 11/13] Add LDesc() accessor method to SolvableVersion Jon Turney
2018-08-05 22:12 ` [PATCH setup 12/13] Restore packagemeta::LDesc() Jon Turney
2018-08-05 22:12 ` [PATCH setup 13/13] Add ldesc tooltips to sdesc column of listview Jon Turney
2018-08-06 14:15 ` [PATCH setup 00/13] ListView Package Chooser Ken Brown
2018-08-06 16:41   ` Achim Gratz
2018-08-06 16:47     ` Achim Gratz
2018-08-06 19:19   ` Ken Brown
2018-10-13 18:46     ` Jon Turney
2018-08-06 16:40 ` Achim Gratz

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).