[PATCH setup 07/13] Custom draw popup menus in ListView control

Jon Turney jon.turney@dronecode.org.uk
Sun Aug 5 22:10:00 GMT 2018


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



More information about the Cygwin-apps mailing list