From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 128084 invoked by alias); 13 Oct 2018 18:13:17 -0000 Mailing-List: contact cygwin-apps-cvs-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Post: List-Help: , Sender: cygwin-apps-cvs-owner@sourceware.org Received: (qmail 128028 invoked by uid 9795); 13 Oct 2018 18:13:17 -0000 Date: Sat, 13 Oct 2018 18:13:00 -0000 Message-ID: <20181013181317.127960.qmail@sourceware.org> From: jturney@sourceware.org To: cygwin-apps-cvs@sourceware.org Subject: [setup - the official Cygwin setup program] branch master, updated. release_2.894 X-Git-Refname: refs/heads/master X-Git-Reftype: branch X-Git-Oldrev: f318f2722d5b0c82779a7240f65a6bbc2db5d58f X-Git-Newrev: 7a524bf7194058086410dc442a7cc1405c6f92a0 X-SW-Source: 2018-q4/txt/msg00005.txt.bz2 https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=7a524bf7194058086410dc442a7cc1405c6f92a0 commit 7a524bf7194058086410dc442a7cc1405c6f92a0 Merge: f318f27 dff279e Author: Jon Turney Date: Sat Oct 13 18:56:11 2018 +0100 Merge branch 'topic/listview' https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=dff279e20df996197676a2f1709e7039ee16b5f9 commit dff279e20df996197676a2f1709e7039ee16b5f9 Author: Ken Brown Date: Mon Aug 6 10:05:12 2018 -0400 Ensure that an installed packageversion has an ldesc if possible In packagedb::read(), copy the ldesc from setup.ini to the packageversion read from installed.db. Otherwise, an installed package with only one version will not have an ldesc to use as a tooltip. v2: If the installed version is no longer available, copy the ldesc from the current version of the package. https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=800274fd5e88c6d48f6315d05bbbd1501bb8cefd commit 800274fd5e88c6d48f6315d05bbbd1501bb8cefd Author: Jon Turney Date: Fri Jul 20 12:52:20 2018 +0100 Add ldesc tooltips to sdesc column of listview Listview's built-in tooltip support doesn't support subitems, so we have to build our own v2: Use string cache https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=e8f2c078b53dc4d56a8903a687deac3be18406b5 commit e8f2c078b53dc4d56a8903a687deac3be18406b5 Author: Jon Turney Date: Wed Jul 18 20:50:31 2018 +0100 Restore packagemeta::LDesc() v2: Update for removed 'usiing namespace std' https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=30cae42fa9bfce260b88550cbb568d578c958e61 commit 30cae42fa9bfce260b88550cbb568d578c958e61 Author: Jon Turney Date: Sun Jul 15 15:41:12 2018 +0100 Add LDesc() accessor method to SolvableVersion https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=68487c7ca5820de12d66370bb2c2dc1ddad0e1df commit 68487c7ca5820de12d66370bb2c2dc1ddad0e1df Author: Jon Turney Date: Tue Jul 17 11:09:32 2018 +0100 Use indents in category view We keep around an empty imagelist for 1x1 images, to reset the indent when not in tree view mode (This isn't quite right as the listview will allocate a 1-pixel space before column 0 for those images, but this seems the best we can do as 0x0 imagelists aren't allowed...) https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=db1c5abc5adae534968e941cef6e9286b96756e4 commit db1c5abc5adae534968e941cef6e9286b96756e4 Author: Jon Turney Date: Tue Jul 17 19:26:23 2018 +0100 Use an icon to represent expanded/collapsed state Use a listview icon to represent expanded/collapsed state, rather than puttting '[+]'/'[-]' in front of the category name Just use the item icon directly to represent expanded/collapsed state, rather than using a state icon as otherwise we have empty space where the item icon would be, when it's size is being used for indenting. The icons were made by tracing the previously used .bmp in Inkscape to produce a .svg, then converting that to an icon with ImageMagick using 'convert -filter point -background transparent -define icon:auto-resize' v2: Drop execute mode (Achim Gratz) https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=b8461b560c32425026382b4f3f191be962b984ae commit b8461b560c32425026382b4f3f191be962b984ae Author: Jon Turney Date: Mon Jul 16 20:02:25 2018 +0100 Show the count of packages in a category https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=4018914c6c98b0d977554619d36b62f709e32ece commit 4018914c6c98b0d977554619d36b62f709e32ece Author: Jon Turney Date: Wed Dec 7 19:52:58 2016 +0000 Custom draw popup menus in ListView control Construct a menu containing the actions from the action list for the package or category, and if one is selected, apply it. This lets us remove packagemeta::set_action() which implements the strange UX of cycling around actions without showing what the possibilities are. This somewhat emulates a BS_SPLITBUTTON style control. The 'popup' cell has a visual hint that clicking on it opens a menu (a 'combox scrollbar'), and the popup menu is located at the position of the click. v2: Factor out popup_menu, for future use by keyboard accelerators https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=8ba85a54652328a92c1eb68e898baf70ba05b525 commit 8ba85a54652328a92c1eb68e898baf70ba05b525 Author: Jon Turney Date: Thu Dec 8 17:57:59 2016 +0000 Add methods for listing possible actions on, and applying one to, a package The 'action id' is a value of the _actions enum, if positive, otherwise it's absolute value is a 0-based index into the 'versions' collection to identify a specific version to install. v2: Fix select_action() to consider deftrust v3: Fix select_action() for keep v4: Update for removed 'using namespace std' https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=61183f1540e036e69abb6347dc7bb134257d442c commit 61183f1540e036e69abb6347dc7bb134257d442c Author: Jon Turney Date: Wed Dec 7 16:17:12 2016 +0000 Custom draw checkboxes in ListView control Future work: What does this look like when a theme is in used? Does the row size need to be slightly adjusted to ensure it accomodates checkbox height? v2: erase to background colour before drawing checkbox https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=ce9f6dd0e4b7938c0919fe0efb06e4efe67242ca commit ce9f6dd0e4b7938c0919fe0efb06e4efe67242ca Author: Jon Turney Date: Wed Dec 7 14:09:35 2016 +0000 Use a ListView common control rather than a hand-built grid Each line is implemented by an object of a subclass of ListViewLine, either PickPackageLine, or (in catgeory view) PickCategoryLine v2: Defer constructing category tree until packagedb is available Also handle an empty category tree Remove unused bitmaps Scoping of strings returned to LVN_GETDISPINFO Avoid doubled 'all' category Update some number of lines, rather than just the current one v3: Speed up calculating column widths, by caching the DC used Don't bother recalculating column widths when obsolete packages are shown/hidden, just allow for obsolete packages always (which makes hardly any difference) Don't bother calculating column widths separately for category view since they are now always the same. v4: Store current category action in CategoryTree Recurse category action onto packages This slightly changes the behaviour: previously, a category action only effected packages which matched the name search filter. Now all packages in contained by the category are effected. v5: When updating, turn off redraw before emptying listview Make headers a parameter to init Don't leak contents Only resize columns once Remove ListView OnMessage as it has nothing to do Never use a column width less than minimum Use LVS_SINGLESEL Factor out string cache for re-use Preserve focused row over ListView::setContents() https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=ffc18a6962f5b2c7c3c06601c02c29f8d65866c7 commit ffc18a6962f5b2c7c3c06601c02c29f8d65866c7 Author: Jon Turney Date: Fri Sep 16 19:23:26 2016 +0100 Add OnNotify virtual function to class Window for WM_NOTIFY notifications Add OnNotify virtual function to class Window for WM_NOTIFY notifications Note that the result is returned via DWLP_MSGRESULT https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=9ae1ac43efb7d409583bf55537896e58a1812d4b commit 9ae1ac43efb7d409583bf55537896e58a1812d4b Author: Jon Turney Date: Sun Jul 15 18:02:08 2018 +0100 Change packagemeta::_actions to an enum Diff: --- ActionList.h | 55 ++++ ListView.cc | 585 +++++++++++++++++++++++++++++++++ ListView.h | 96 ++++++ Makefile.am | 12 +- PickCategoryLine.cc | 165 +++------- PickCategoryLine.h | 81 ++---- PickLine.h | 47 --- PickPackageLine.cc | 150 +++++----- PickPackageLine.h | 31 +- PickView.cc | 888 +++++++++------------------------------------------ PickView.h | 217 +++++++------ check-na.bmp | Bin 106 -> 0 bytes check-no.bmp | Bin 106 -> 0 bytes check-yes.bmp | Bin 106 -> 0 bytes choose-spin.bmp | Bin 106 -> 0 bytes choose.cc | 71 +++-- choose.h | 6 +- libsolv.cc | 14 + libsolv.h | 1 + main.cc | 2 +- package_db.cc | 2 + package_meta.cc | 155 +++++----- package_meta.h | 38 +-- proppage.cc | 14 +- res.rc | 19 +- resource.h | 13 +- tree-minus.bmp | Bin 106 -> 0 bytes tree-minus.ico | Bin 0 -> 299654 bytes tree-minus.svg | 118 +++++++ tree-plus.bmp | Bin 106 -> 0 bytes tree-plus.ico | Bin 0 -> 299671 bytes tree-plus.svg | 126 ++++++++ window.h | 7 + 33 files changed, 1606 insertions(+), 1307 deletions(-) diff --git a/ActionList.h b/ActionList.h new file mode 100644 index 0000000..2e2d424 --- /dev/null +++ b/ActionList.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 Jon Turney + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * A copy of the GNU General Public License can be found at + * http://www.gnu.org/ + * + */ + +#ifndef SETUP_ACTIONLIST_H +#define SETUP_ACTIONLIST_H + +#include +#include + +// --------------------------------------------------------------------------- +// 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 Actions; + +class ActionList +{ + public: + void add(const std::string &name, int id, bool selected, bool enabled) + { + Action act(name, id, selected, enabled); + list.push_back(act); + }; + Actions list; +}; + +#endif /* SETUP_ACTIONLIST_H */ diff --git a/ListView.cc b/ListView.cc new file mode 100644 index 0000000..e287270 --- /dev/null +++ b/ListView.cc @@ -0,0 +1,585 @@ +/* + * Copyright (c) 2016 Jon Turney + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * A copy of the GNU General Public License can be found at + * http://www.gnu.org/ + * + */ + +#include "ListView.h" +#include "LogSingleton.h" +#include "resource.h" + +#include + +// --------------------------------------------------------------------------- +// 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 ("Working..."); + ListView_InsertItem(hWndListView, &lvi); + + // populate with columns + initColumns(headers); + + // create a small icon imagelist + // (the order of images matches ListViewLine::State enum) + hImgList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + ILC_COLOR32, 2, 0); + ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_PLUS))); + ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_MINUS))); + + // create an empty imagelist, used to reset the indent + hEmptyImgList = ImageList_Create(1, 1, + ILC_COLOR32, 2, 0); + + // LVS_EX_INFOTIP/LVN_GETINFOTIP doesn't work for subitems, so we have to do + // our own tooltip handling + hWndTip = CreateWindowEx (0, + (LPCTSTR) TOOLTIPS_CLASS, + NULL, + WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + hWndParent, + (HMENU) 0, + GetModuleHandle(NULL), + NULL); + // must be topmost so that tooltips will display on top + SetWindowPos(hWndTip, HWND_TOPMOST,0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + + TOOLINFO ti; + memset ((void *)&ti, 0, sizeof(ti)); + ti.cbSize = sizeof(ti); + ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS; + ti.hwnd = hWndParent; + ti.uId = (UINT_PTR)hWndListView; + ti.lpszText = LPSTR_TEXTCALLBACK; // use TTN_GETDISPINFO + SendMessage(hWndTip, TTM_ADDTOOL, 0, (LPARAM)&ti); + + // match long delay for tooltip to disappear used elsewhere (30s) + SendMessage(hWndTip, TTM_SETDELAYTIME, TTDT_AUTOPOP, (LPARAM) MAKELONG (30000, 0)); + // match tip width used elsewhere + SendMessage(hWndTip, TTM_SETMAXTIPWIDTH, 0, 450); +} + +void +ListView::initColumns(HeaderList headers_) +{ + // store HeaderList for later use + headers = headers_; + + // create the columns + LVCOLUMN lvc; + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + + int i; + for (i = 0; headers[i].text != 0; i++) + { + lvc.iSubItem = i; + lvc.pszText = const_cast (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 <size(); i++) + { + LVITEM lvi; + lvi.mask = LVIF_TEXT | (tree ? LVIF_IMAGE | LVIF_INDENT : 0); + lvi.iItem = i; + lvi.iSubItem = 0; + lvi.pszText = LPSTR_TEXTCALLBACK; + if (tree) + { + lvi.iImage = I_IMAGECALLBACK; + lvi.iIndent = (*contents)[i]->get_indent(); + } + + ListView_InsertItem(hWndListView, &lvi); + } + + if (iRow >= 0) + { + ListView_SetItemState(hWndListView, iRow, LVNI_SELECTED | LVNI_FOCUSED, LVNI_SELECTED | LVNI_FOCUSED); + ListView_EnsureVisible(hWndListView, iRow, false); + } + + // enable redrawing of ListView and redraw + SendMessage(hWndListView, WM_SETREDRAW, TRUE, 0); + RedrawWindow(hWndListView, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); +} + +// Helper class: The char * pointer we hand back needs to remain valid for some +// time after OnNotify returns, when the std::string we have retrieved has gone +// out of scope, so a static instance of this class maintains a local cache. +class StringCache +{ +public: + StringCache() : cache(NULL), cache_size(0) { } + StringCache & operator = (const std::string & s) + { + if ((s.length() + 1) > cache_size) + { + cache_size = s.length() + 1; + cache = (char *)realloc(cache, cache_size); + } + strcpy(cache, s.c_str()); + return *this; + } + operator char *() const + { + return cache; + } +private: + char *cache; + size_t cache_size; +}; + +bool +ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult) +{ +#if DEBUG + Log (LOG_BABBLE) << "ListView::OnNotify id:" << pNmHdr->idFrom << " hwnd:" << pNmHdr->hwndFrom << " code:" << (int)pNmHdr->code << endLog; +#endif + + switch (pNmHdr->code) + { + case LVN_GETDISPINFO: + { + NMLVDISPINFO *pNmLvDispInfo = (NMLVDISPINFO *)pNmHdr; +#if DEBUG + Log (LOG_BABBLE) << "LVN_GETDISPINFO " << pNmLvDispInfo->item.iItem << endLog; +#endif + if (contents) + { + int iRow = pNmLvDispInfo->item.iItem; + int iCol = pNmLvDispInfo->item.iSubItem; + + static StringCache s; + s = (*contents)[iRow]->get_text(iCol); + pNmLvDispInfo->item.pszText = s; + + if (pNmLvDispInfo->item.iSubItem == 0) + { + pNmLvDispInfo->item.iImage = (int)((*contents)[pNmLvDispInfo->item.iItem]->get_state()); + } + } + + return true; + } + break; + + case LVN_GETEMPTYMARKUP: + { + NMLVEMPTYMARKUP *pNmMarkup = (NMLVEMPTYMARKUP*) pNmHdr; + + MultiByteToWideChar(CP_UTF8, 0, + empty_list_text, -1, + pNmMarkup->szMarkup, L_MAX_URL_LENGTH); + + *pResult = true; + return true; + } + break; + + case NM_CLICK: + { + NMITEMACTIVATE *pNmItemAct = (NMITEMACTIVATE *) pNmHdr; +#if DEBUG + Log (LOG_BABBLE) << "NM_CLICK: pnmitem->iItem " << pNmItemAct->iItem << " pNmItemAct->iSubItem " << pNmItemAct->iSubItem << endLog; +#endif + int iRow = pNmItemAct->iItem; + int iCol = pNmItemAct->iSubItem; + if (iRow < 0) + return false; + + int update = 0; + + if (headers[iCol].type == ListView::ControlType::popup) + { + POINT p; + // position pop-up menu at the location of the click + GetCursorPos(&p); + + update = popup_menu(iRow, iCol, p); + } + else + { + // Inform the item of the click + update = (*contents)[iRow]->do_action(iCol, 0); + } + + // Update items, if needed + if (update > 0) + { + ListView_RedrawItems(hWndListView, iRow, iRow + update -1); + } + + return true; + } + break; + + case NM_CUSTOMDRAW: + { + NMLVCUSTOMDRAW *pNmLvCustomDraw = (NMLVCUSTOMDRAW *)pNmHdr; + + switch(pNmLvCustomDraw->nmcd.dwDrawStage) + { + case CDDS_PREPAINT: + *pResult = CDRF_NOTIFYITEMDRAW; + return true; + case CDDS_ITEMPREPAINT: + *pResult = CDRF_NOTIFYSUBITEMDRAW; + return true; + case CDDS_SUBITEM | CDDS_ITEMPREPAINT: + { + LRESULT result = CDRF_DODEFAULT; + int iCol = pNmLvCustomDraw->iSubItem; + int iRow = pNmLvCustomDraw->nmcd.dwItemSpec; + + switch (headers[iCol].type) + { + default: + case ListView::ControlType::text: + result = CDRF_DODEFAULT; + break; + + case ListView::ControlType::checkbox: + { + // get the subitem text + char buf[3]; + ListView_GetItemText(hWndListView, iRow, iCol, buf, _countof(buf)); + + // map the subitem text to a checkbox state + UINT state = DFCS_BUTTONCHECK | DFCS_FLAT; + if (buf[0] == '\0') // empty + { + result = CDRF_DODEFAULT; + break; + } + else if (buf[0] == 'y') // yes + state |= DFCS_CHECKED; + else if ((buf[0] == 'n') && (buf[1] == 'o')) // no + state |= 0; + else // n/a + state |= DFCS_INACTIVE; + + // erase and draw a checkbox + RECT r; + ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r); + HBRUSH hBrush = CreateSolidBrush(ListView_GetBkColor(hWndListView)); + FillRect(pNmLvCustomDraw->nmcd.hdc, &r, hBrush); + DeleteObject(hBrush); + DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_BUTTON, state); + + result = CDRF_SKIPDEFAULT; + } + break; + + case ListView::ControlType::popup: + { + // let the control draw the text, but notify us afterwards + result = CDRF_NOTIFYPOSTPAINT; + } + break; + } + + *pResult = result; + return true; + } + case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT: + { + LRESULT result = CDRF_DODEFAULT; + int iCol = pNmLvCustomDraw->iSubItem; + int iRow = pNmLvCustomDraw->nmcd.dwItemSpec; + + switch (headers[iCol].type) + { + default: + result = CDRF_DODEFAULT; + break; + + case ListView::ControlType::popup: + { + // draw the control at the RHS of the cell + RECT r; + ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r); + r.left = r.right - GetSystemMetrics(SM_CXVSCROLL); + DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_SCROLL,DFCS_SCROLLCOMBOBOX); + + result = CDRF_DODEFAULT; + } + break; + } + *pResult = result; + return true; + } + } + } + break; + + case LVN_HOTTRACK: + { + NMLISTVIEW *pNmListView = (NMLISTVIEW *)pNmHdr; + int iRow = pNmListView->iItem; + int iCol = pNmListView->iSubItem; +#if DEBUG + Log (LOG_BABBLE) << "LVN_HOTTRACK " << iRow << " " << iCol << endLog; +#endif + if (iRow < 0) + return true; + + // if we've tracked off to a different cell + if ((iRow != iRow_track) || (iCol != iCol_track)) + { +#if DEBUG + Log (LOG_BABBLE) << "LVN_HOTTRACK changed cell" << endLog; +#endif + + // if the tooltip for previous cell is displayed, remove it + // restart the tooltip AUTOPOP timer for this cell + SendMessage(hWndTip, TTM_ACTIVATE, FALSE, 0); + SendMessage(hWndTip, TTM_ACTIVATE, TRUE, 0); + + iRow_track = iRow; + iCol_track = iCol; + } + + return true; + } + break; + + case TTN_GETDISPINFO: + { + // convert mouse position to item/subitem + LVHITTESTINFO lvHitTestInfo; + lvHitTestInfo.flags = LVHT_ONITEM; + GetCursorPos(&lvHitTestInfo.pt); + ::ScreenToClient(hWndListView, &lvHitTestInfo.pt); + ListView_SubItemHitTest(hWndListView, &lvHitTestInfo); + + int iRow = lvHitTestInfo.iItem; + int iCol = lvHitTestInfo.iSubItem; + if (iRow < 0) + return false; + +#if DEBUG + Log (LOG_BABBLE) << "TTN_GETDISPINFO " << iRow << " " << iCol << endLog; +#endif + + // get the tooltip text for that item/subitem + static StringCache tooltip; + tooltip = ""; + if (contents) + tooltip = (*contents)[iRow]->get_tooltip(iCol); + + // set the tooltip text + NMTTDISPINFO *pNmTTDispInfo = (NMTTDISPINFO *)pNmHdr; + pNmTTDispInfo->lpszText = tooltip; + pNmTTDispInfo->hinst = NULL; + pNmTTDispInfo->uFlags = 0; + + return true; + } + break; + } + + // We don't care. + return false; +} + +void +ListView::empty(void) +{ + ListView_DeleteAllItems(hWndListView); +} + +void +ListView::setEmptyText(const char *text) +{ + empty_list_text = text; +} + +int +ListView::popup_menu(int iRow, int iCol, POINT p) +{ + int update = 0; + // construct menu + HMENU hMenu = CreatePopupMenu(); + + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_FTYPE | MIIM_STATE | MIIM_STRING | MIIM_ID; + mii.fType = MFT_STRING; + + ActionList *al = (*contents)[iRow]->get_actions(iCol); + + Actions::iterator i; + int j = 1; + for (i = al->list.begin (); i != al->list.end (); ++i, ++j) + { + BOOL res; + mii.dwTypeData = (char *)i->name.c_str(); + mii.fState = (i->selected ? MFS_CHECKED : MFS_UNCHECKED | + i->enabled ? MFS_ENABLED : MFS_DISABLED); + mii.wID = j; + + res = InsertMenuItem(hMenu, -1, TRUE, &mii); + if (!res) Log (LOG_BABBLE) << "InsertMenuItem failed " << endLog; + } + + int id = TrackPopupMenu(hMenu, + TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_NOANIMATION, + p.x, p.y, 0, hWndListView, NULL); + + // Inform the item of the menu choice + if (id) + update = (*contents)[iRow]->do_action(iCol, al->list[id-1].id); + + DestroyMenu(hMenu); + delete al; + + return update; +} diff --git a/ListView.h b/ListView.h new file mode 100644 index 0000000..b4fd1fd --- /dev/null +++ b/ListView.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2016 Jon Turney + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * A copy of the GNU General Public License can be found at + * http://www.gnu.org/ + * + */ + +#ifndef SETUP_LISTVIEW_H +#define SETUP_LISTVIEW_H + +#include "ActionList.h" +#include "win32.h" +#include +#include + +// --------------------------------------------------------------------------- +// interface to class ListView +// +// ListView Common Control +// --------------------------------------------------------------------------- + +class ListViewLine +{ + public: + enum class State { collapsed, expanded, nothing=-1 }; + + virtual ~ListViewLine() {}; + virtual const std::string get_text(int col) const = 0; + virtual State get_state() const = 0; + virtual const std::string get_tooltip(int col) const = 0; + virtual int get_indent() const = 0; + virtual ActionList *get_actions(int col) const = 0; + virtual int do_action(int col, int id) = 0; +}; + +typedef std::vector ListViewContents; + +class ListView +{ + public: + enum class ControlType + { + text, + checkbox, + popup, + }; + + class Header + { + public: + const char *text; + int fmt; + ControlType type; + int width; + int hdr_width; + }; + typedef Header *HeaderList; + + void init(HWND parent, int id, HeaderList headers); + + void noteColumnWidthStart(); + void noteColumnWidth(int col_num, const std::string& string); + void noteColumnWidthEnd(); + void resizeColumns(void); + + void setContents(ListViewContents *contents, bool tree = false); + void setEmptyText(const char *text); + + bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult); + + private: + HWND hWndParent; + HWND hWndListView; + HWND hWndTip; + HDC dc; + HIMAGELIST hImgList; + HIMAGELIST hEmptyImgList; + + ListViewContents *contents; + HeaderList headers; + const char *empty_list_text; + int iRow_track; + int iCol_track; + + void initColumns(HeaderList hl); + void empty(void); + int popup_menu(int iRow, int iCol, POINT p); +}; + +#endif /* SETUP_LISTVIEW_H */ diff --git a/Makefile.am b/Makefile.am index 0ad4e5c..3c41389 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,17 +43,13 @@ EXTRA_DIST = \ CONTRIBUTORS \ COPYING \ bootstrap.sh \ - check-na.bmp \ - check-no.bmp \ - check-yes.bmp \ - choose-spin.bmp \ cygwin.ico \ cygwin-setup.ico \ cygwin-terminal.ico \ setup.exe.manifest \ setup64.exe.manifest \ - tree-minus.bmp \ - tree-plus.bmp + tree-minus.ico \ + tree-plus.ico # iniparse.hh is generated from iniparse.yy via bison -d, so it needs to be # included here for proper tracking (but not iniparse.cc, since automake @@ -108,6 +104,7 @@ inilint_SOURCES = \ -lshlwapi -lcomctl32 -lole32 -lpsapi -luuid -lntdll -lwininet -lws2_32 -lmingw32 @SETUP@_LDFLAGS = -mwindows -Wc,-static -static-libtool-libs @SETUP@_SOURCES = \ + actionlist.h \ AntiVirus.cc \ AntiVirus.h \ archive.cc \ @@ -181,6 +178,8 @@ inilint_SOURCES = \ KeysSetting.h \ libsolv.cc \ libsolv.h \ + ListView.cc \ + ListView.h \ localdir.cc \ localdir.h \ LogFile.cc \ @@ -216,7 +215,6 @@ inilint_SOURCES = \ PackageTrust.h \ PickCategoryLine.cc \ PickCategoryLine.h \ - PickLine.h \ PickPackageLine.cc \ PickPackageLine.h \ PickView.cc \ diff --git a/PickCategoryLine.cc b/PickCategoryLine.cc index 24fecb3..1dbecf2 100644 --- a/PickCategoryLine.cc +++ b/PickCategoryLine.cc @@ -16,134 +16,75 @@ #include "PickCategoryLine.h" #include "package_db.h" #include "PickView.h" +#include "window.h" +#include "package_meta.h" +#include -void -PickCategoryLine::empty (void) +const std::string +PickCategoryLine::get_text (int col_num) const { - while (bucket.size ()) + if (col_num == pkgname_col) { - PickLine *line = *bucket.begin (); - delete line; - bucket.erase (bucket.begin ()); + std::ostringstream s; + s << cat_tree->category().first; + if (pkgcount) + s << " (" << pkgcount << ")"; + return s.str(); } + else if (col_num == new_col) + { + return packagemeta::action_caption (cat_tree->action()); + } + return ""; } -void -PickCategoryLine::paint (HDC hdc, HRGN hUpdRgn, int x, int y, int row, int show_cat) +int +PickCategoryLine::do_action(int col_num, int action_id) { - int r = y + row * theView.row_height; - if (show_label) - { - int x2 = x + theView.headers[theView.cat_col].x + HMARGIN / 2 + depth * TREE_INDENT; - int by = r + (theView.tm.tmHeight / 2) - 5; - - // draw the '+' or '-' box - theView.DrawIcon (hdc, x2, by, (collapsed ? theView.bm_treeplus : theView.bm_treeminus)); - - // draw the category name - TextOut (hdc, x2 + 11 + ICON_MARGIN, r, cat.first.c_str(), cat.first.size()); - if (!labellength) - { - SIZE s; - GetTextExtentPoint32 (hdc, cat.first.c_str(), cat.first.size(), &s); - labellength = s.cx; - } - - // draw the 'spin' glyph - spin_x = x2 + 11 + ICON_MARGIN + labellength + ICON_MARGIN; - theView.DrawIcon (hdc, spin_x, by, theView.bm_spin); - - // draw the caption ('Default', 'Install', etc) - TextOut (hdc, spin_x + SPIN_WIDTH + ICON_MARGIN, r, - current_default.caption (), strlen (current_default.caption ())); - row++; - } - if (collapsed) - return; - - // are the siblings containers? - if (bucket.size () && bucket[0]->IsContainer ()) + if (col_num == pkgname_col) { - for (size_t n = 0; n < bucket.size (); n++) - { - bucket[n]->paint (hdc, hUpdRgn, x, y, row, show_cat); - row += bucket[n]->itemcount (); - } + cat_tree->collapsed() = ! cat_tree->collapsed(); + theView.refresh(); } - else + else if (col_num == new_col) { - // calculate the maximum y value we expect for this group of lines - int max_y = y + (row + bucket.size ()) * theView.row_height; - - // paint all contained rows, columnwise - for (int i = 0; theView.headers[i].text; i++) - { - RECT r; - r.left = x + theView.headers[i].x; - r.right = r.left + theView.headers[i].width; - - // set up a clipping mask if necessary - if (theView.headers[i].needs_clip) - IntersectClipRect (hdc, r.left, y, r.right, max_y); - - // draw each row in this column - for (unsigned int n = 0; n < bucket.size (); n++) - { - // test for visibility - r.top = y + ((row + n) * theView.row_height); - r.bottom = r.top + theView.row_height; - if (RectVisible (hdc, &r) != 0) - bucket[n]->paint (hdc, hUpdRgn, (int)r.left, (int)r.top, i, show_cat); - } - - // restore original clipping area - if (theView.headers[i].needs_clip) - SelectClipRgn (hdc, hUpdRgn); - } + theView.GetParent ()->SetBusy (); + int u = cat_tree->do_action((packagemeta::_actions)(action_id), theView.deftrust); + theView.GetParent ()->ClearBusy (); + return u; } + return 1; +} + +ActionList * +PickCategoryLine::get_actions(int col) const +{ + ActionList *al = new ActionList(); + packagemeta::_actions current_default = cat_tree->action(); + + al->add("Default", (int)packagemeta::Default_action, (current_default == packagemeta::Default_action), TRUE); + al->add("Install", (int)packagemeta::Install_action, (current_default == packagemeta::Install_action), TRUE); + al->add(packagedb::task == PackageDB_Install ? "Reinstall" : "Retrieve", + (int)packagemeta::Reinstall_action, (current_default == packagemeta::Reinstall_action), TRUE); + al->add("Uninstall", (int)packagemeta::Uninstall_action, (current_default == packagemeta::Uninstall_action), TRUE); + + return al; +} + +ListViewLine::State +PickCategoryLine::get_state() const +{ + return cat_tree->collapsed() ? State::collapsed : State::expanded; } int -PickCategoryLine::click (int const myrow, int const ClickedRow, int const x) +PickCategoryLine::get_indent() const { - if (myrow == ClickedRow && show_label) - { - if ((size_t) x >= spin_x) - { - ++current_default; - - return set_action (current_default); - } - else - { - collapsed = !collapsed; - int accum_row = 0; - for (size_t n = 0; n < bucket.size (); ++n) - accum_row += bucket[n]->itemcount (); - return collapsed ? accum_row : -accum_row; - } - } - else - { - int accum_row = myrow + (show_label ? 1 : 0); - for (size_t n = 0; n < bucket.size (); ++n) - { - if (accum_row + bucket[n]->itemcount () > ClickedRow) - return bucket[n]->click (accum_row, ClickedRow, x); - accum_row += bucket[n]->itemcount (); - } - return 0; - } + return indent; } -int -PickCategoryLine::set_action (packagemeta::_actions action) +const std::string +PickCategoryLine::get_tooltip(int col_num) const { - theView.GetParent ()->SetBusy (); - current_default = action; - int accum_diff = 0; - for (size_t n = 0; n < bucket.size (); n++) - accum_diff += bucket[n]->set_action (current_default); - theView.GetParent ()->ClearBusy (); - return accum_diff; + return ""; } diff --git a/PickCategoryLine.h b/PickCategoryLine.h index dcffbac..9c5e996 100644 --- a/PickCategoryLine.h +++ b/PickCategoryLine.h @@ -16,75 +16,36 @@ #ifndef SETUP_PICKCATEGORYLINE_H #define SETUP_PICKCATEGORYLINE_H - -class PickView; -#include -#include "PickLine.h" #include "package_meta.h" +#include "ListView.h" +#include "PickView.h" -class PickCategoryLine:public PickLine +class PickCategoryLine: public ListViewLine { public: - PickCategoryLine (PickView & aView, Category & _cat, size_t thedepth = 0, bool aBool = - true, bool aBool2 = - true):PickLine (_cat.first), - current_default (packagemeta::Default_action), cat (_cat), labellength (0), - depth (thedepth), theView (aView) + PickCategoryLine (PickView & aView, CategoryTree * _tree, int _pkgcount, int _indent) : + cat_tree (_tree), + pkgcount(_pkgcount), + theView (aView), + indent(_indent) { - if (aBool) - { - collapsed = true; - show_label = true; - } - else - { - collapsed = false; - show_label = aBool2; - } }; ~PickCategoryLine () { - empty (); - } - void ShowLabel (bool aBool = true) - { - show_label = aBool; - if (!show_label) - collapsed = false; - } - virtual void paint (HDC hdc, HRGN hUpdRgn, int x, int y, int row, int show_cat); - virtual int click (int const myrow, int const ClickedRow, int const x); - virtual int itemcount () const - { - if (collapsed) - return 1; - int t = show_label ? 1 : 0; - for (size_t n = 0; n < bucket.size (); ++n) - t += bucket[n]->itemcount (); - return t; - }; - virtual bool IsContainer (void) const - { - return true; - } - virtual void insert (PickLine & aLine) - { - bucket.push_back (&aLine); } - void empty (); - virtual int set_action (packagemeta::_actions); + + const std::string get_text(int col) const; + State get_state() const; + const std::string get_tooltip(int col) const; + int get_indent() const; + ActionList *get_actions(int col) const; + int do_action(int col, int action_id); + private: - packagemeta::_actions - current_default; - Category & cat; - bool collapsed; - bool show_label; - size_t labellength; - size_t spin_x; // x-coord where the spin button starts - size_t depth; - PickCategoryLine (PickCategoryLine const &); - PickCategoryLine & operator= (PickCategoryLine const &); - std::vector < PickLine * > bucket; - PickView& theView; + CategoryTree * cat_tree; + int pkgcount; + PickView & theView; + int indent; }; + #endif /* SETUP_PICKCATEGORYLINE_H */ diff --git a/PickLine.h b/PickLine.h deleted file mode 100644 index 7cf84cb..0000000 --- a/PickLine.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2002 Robert Collins. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * A copy of the GNU General Public License can be found at - * http://www.gnu.org/ - * - * Written by Robert Collins - * - */ - -#ifndef SETUP_PICKLINE_H -#define SETUP_PICKLINE_H - -#include "win32.h" -#include "package_meta.h" - -class PickLine -{ -public: - virtual void paint (HDC hdc, HRGN hUpdRgn, int x, int y, int col_num, int show_cat) = 0; - virtual int click (int const myrow, int const ClickedRow, int const x) = 0; - virtual int set_action (packagemeta::_actions) = 0; - virtual int itemcount () const = 0; - // this may indicate bad inheritance model. - virtual bool IsContainer (void) const = 0; - virtual void insert (PickLine &) = 0; - const std::string key; - virtual ~ PickLine () - { - }; -protected: - PickLine () - { - }; - PickLine (const std::string& aKey) : key (aKey) - { - }; - PickLine (PickLine const &); - PickLine & operator= (PickLine const &); -}; - -#endif /* SETUP_PICKLINE_H */ diff --git a/PickPackageLine.cc b/PickPackageLine.cc index 2caeafe..133720b 100644 --- a/PickPackageLine.cc +++ b/PickPackageLine.cc @@ -17,70 +17,54 @@ #include "PickView.h" #include "package_db.h" -void -PickPackageLine::paint (HDC hdc, HRGN unused, int x, int y, int col_num, int show_cat) +const std::string +PickPackageLine::get_text(int col_num) const { - int rb = y + theView.tm.tmHeight; - int by = rb - 11; // top of box images - std::string s; - - if (col_num == theView.current_col && pkg.installed) + if (col_num == pkgname_col) + { + return pkg.name; + } + else if (col_num == current_col) { - TextOut (hdc, x + HMARGIN/2, y, pkg.installed.Canonical_version ().c_str(), - pkg.installed.Canonical_version ().size()); + return pkg.installed.Canonical_version (); } - else if (col_num == theView.new_col) + else if (col_num == new_col) { - // TextOut (hdc, x + HMARGIN/2 + NEW_COL_SIZE_SLOP, y, s.c_str(), s.size()); - // theView.DrawIcon (hdc, x + HMARGIN/2 + ICON_MARGIN/2 + RTARROW_WIDTH, by, theView.bm_spin); - TextOut (hdc, x + HMARGIN/2 + ICON_MARGIN/2 + SPIN_WIDTH , y, - pkg.action_caption ().c_str(), pkg.action_caption ().size()); - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_spin); + return pkg.action_caption (); } - else if (col_num == theView.bintick_col) + else if (col_num == bintick_col) { + const char *bintick = "?"; if (/* uninstall or skip */ !pkg.desired || /* current version */ pkg.desired == pkg.installed || /* no source */ !pkg.desired.accessible()) - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkna); + bintick = "n/a"; else if (pkg.picked()) - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkyes); + bintick = "yes"; else - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkno); + bintick = "no"; + + return bintick; } - else if (col_num == theView.srctick_col) + else if (col_num == srctick_col) { + const char *srctick = "?"; if ( /* uninstall */ !pkg.desired || - -#if 0 - /* note: I'm not sure what the logic here is. With this following - check enabled, clicking on the "source" box for a package that - is already installed results it in showing "n/a", instead of a - cross-box. That seems very unintuitive, it should show a cross- - box to indicate that the source is going to be downloaded and - unpacked. Disabling this, but leaving the code as reference - in case there is some reason I'm missing for having it. --b.d. */ - /* source only */ (!pkg.desired.picked() - && pkg.desired.sourcePackage().picked() && pkg.desired == pkg.installed) || -#endif - /* when no source mirror available */ - !pkg.desired.sourcePackage().accessible()) - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkna); + /* when no source mirror available */ + !pkg.desired.sourcePackage().accessible()) + srctick = "n/a"; else if (pkg.srcpicked()) - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkyes); + srctick = "yes"; else - theView.DrawIcon (hdc, x + HMARGIN/2, by, theView.bm_checkno); + srctick = "no"; + + return srctick; } - else if (col_num == theView.cat_col) + else if (col_num == cat_col) { - /* shows "first" category - do we want to show any? */ - if (pkg.categories.size () && show_cat) - { - s = pkg.getReadableCategoryList(); - TextOut (hdc, x + HMARGIN / 2, y, s.c_str(), s.size()); - } + return pkg.getReadableCategoryList(); } - else if (col_num == theView.size_col) + else if (col_num == size_col) { int sz = 0; packageversion picked; @@ -103,62 +87,76 @@ PickPackageLine::paint (HDC hdc, HRGN unused, int x, int y, int col_num, int sho sz += picked.sourcePackage().source()->size; /* If size still 0, size must be unknown. */ - s = (sz == 0) ? "?" : format_1000s((sz+1023)/1024) + "k"; - SIZE tw; - GetTextExtentPoint32 (hdc, s.c_str(), s.size(), &tw); - int cw = theView.headers[col_num].width - HMARGIN - tw.cx; - TextOut (hdc, x + cw + HMARGIN / 2, y, s.c_str(), s.size()); + std::string size = (sz == 0) ? "?" : format_1000s((sz+1023)/1024) + "k"; + + return size; + } + else if (col_num == pkg_col) + { + return pkg.SDesc(); } - else if (col_num == theView.pkg_col) + + return "unknown"; +} + +const std::string +PickPackageLine::get_tooltip(int col_num) const +{ + if (col_num == pkg_col) { - s = pkg.name; - if (pkg.SDesc ().size()) - s += std::string(": ") + std::string(pkg.SDesc()); - TextOut (hdc, x + HMARGIN / 2, y, s.c_str(), s.size()); + return pkg.LDesc(); } + + return ""; } int -PickPackageLine::click (int const myrow, int const ClickedRow, int const x) +PickPackageLine::do_action(int col_num, int action_id) { - // assert (myrow == ClickedRow); - if (x >= theView.headers[theView.new_col].x - HMARGIN / 2 - && x <= theView.headers[theView.new_col + 1].x - HMARGIN / 2) + if (col_num == new_col) { - pkg.set_action (theView.deftrust); - return 0; + pkg.select_action(action_id, theView.deftrust); + return 1; } - if (x >= theView.headers[theView.bintick_col].x - HMARGIN / 2 - && x <= theView.headers[theView.bintick_col + 1].x - HMARGIN / 2) + if (col_num == bintick_col) { if (pkg.desired.accessible ()) - pkg.pick (!pkg.picked ()); + pkg.pick (!pkg.picked ()); } - else if (x >= theView.headers[theView.srctick_col].x - HMARGIN / 2 - && x <= theView.headers[theView.srctick_col + 1].x - HMARGIN / 2) + else if (col_num == srctick_col) { if (pkg.desired.sourcePackage ().accessible ()) - pkg.srcpick (!pkg.srcpicked ()); + pkg.srcpick (!pkg.srcpicked ()); } - /* Unchecking binary while source is unchecked or vice versa is equivalent - to uninstalling. It's essential to set desired correctly, otherwise the + /* Unchecking binary while source is unchecked or vice versa is equivalent to + uninstalling. It's essential to set desired correctly, otherwise the package gets uninstalled without visual feedback to the user. The package will not even show up in the "Pending" view! */ - if ((x >= theView.headers[theView.bintick_col].x - HMARGIN / 2 - && x <= theView.headers[theView.bintick_col + 1].x - HMARGIN / 2) || - (x >= theView.headers[theView.srctick_col].x - HMARGIN / 2 - && x <= theView.headers[theView.srctick_col + 1].x - HMARGIN / 2)) + if ((col_num == bintick_col) || (col_num == srctick_col)) { if (!pkg.picked () && !pkg.srcpicked ()) pkg.desired = packageversion (); + + return 1; } return 0; } -int PickPackageLine::set_action (packagemeta::_actions action) +ActionList * +PickPackageLine::get_actions(int col_num) const +{ + if (col_num == new_col) + { + return pkg.list_actions (theView.deftrust); + } + + return NULL; +} + +int +PickPackageLine::get_indent() const { - pkg.set_action (action, pkg.trustp (true, theView.deftrust)); - return 1; + return indent; } diff --git a/PickPackageLine.h b/PickPackageLine.h index 612bf38..12c7636 100644 --- a/PickPackageLine.h +++ b/PickPackageLine.h @@ -17,32 +17,29 @@ #define SETUP_PICKPACKAGELINE_H class PickView; + #include "package_meta.h" -#include "PickLine.h" +#include "ListView.h" -class PickPackageLine:public PickLine +class PickPackageLine: public ListViewLine { public: - PickPackageLine (PickView &aView, packagemeta & apkg):PickLine (apkg.key), pkg (apkg), theView (aView) - { - }; - virtual void paint (HDC hdc, HRGN unused, int x, int y, int col_num, int show_cat); - virtual int click (int const myrow, int const ClickedRow, int const x); - virtual int itemcount () const - { - return 1; - } - virtual bool IsContainer (void) const - { - return false; - } - virtual void insert (PickLine &) + PickPackageLine (PickView &aView, packagemeta & apkg, int aindent) : + pkg (apkg), + theView (aView), + indent (aindent) { }; - virtual int set_action (packagemeta::_actions); + const std::string get_text(int col) const; + State get_state() const { return State::nothing; } + const std::string get_tooltip(int col) const; + int get_indent() const; + ActionList *get_actions(int col_num) const; + int do_action(int col, int action_id); private: packagemeta & pkg; PickView & theView; + int indent; }; #endif /* SETUP_PICKPACKAGELINE_H */ diff --git a/PickView.cc b/PickView.cc index f8875e4..8412282 100644 --- a/PickView.cc +++ b/PickView.cc @@ -14,12 +14,12 @@ */ #include "PickView.h" +#include "PickPackageLine.h" +#include "PickCategoryLine.h" #include #include -#include #include -#include "PickPackageLine.h" -#include "PickCategoryLine.h" + #include "package_db.h" #include "dialog.h" #include "resource.h" @@ -28,135 +28,25 @@ #include "LogSingleton.h" #include "Exception.h" -static PickView::Header pkg_headers[] = { - {"Current", 0, 0, true}, - {"New", 0, 0, true}, - {"Bin?", 0, 0, false}, - {"Src?", 0, 0, false}, - {"Categories", 0, 0, true}, - {"Size", 0, 0, true}, - {"Package", 0, 0, true}, - {0, 0, 0, false} -}; - -static PickView::Header cat_headers[] = { - {"Category", 0, 0, true}, - {"Current", 0, 0, true}, - {"New", 0, 0, true}, - {"Bin?", 0, 0, false}, - {"Src?", 0, 0, false}, - {"Size", 0, 0, true}, - {"Package", 0, 0, true}, - {0, 0, 0, false} -}; - -ATOM PickView::WindowClassAtom = 0; - -// DoInsertItem - inserts an item into a header control. -// Returns the index of the new item. -// hwndHeader - handle to the header control. -// iInsertAfter - index of the previous item. -// nWidth - width of the new item. -// lpsz - address of the item string. -static int -DoInsertItem (HWND hwndHeader, int iInsertAfter, int nWidth, LPSTR lpsz) -{ - HDITEM hdi; - int index; - - hdi.mask = HDI_TEXT | HDI_FORMAT | HDI_WIDTH; - hdi.pszText = lpsz; - hdi.cxy = nWidth; - hdi.cchTextMax = lstrlen (hdi.pszText); - hdi.fmt = HDF_LEFT | HDF_STRING; - - index = SendMessage (hwndHeader, HDM_INSERTITEM, - (WPARAM) iInsertAfter, (LPARAM) & hdi); - - return index; -} - -int -PickView::set_header_column_order (views vm) -{ - if (vm == views::PackageFull || vm == views::PackagePending - || vm == views::PackageKeeps || vm == views::PackageSkips - || vm == views::PackageUserPicked) - { - headers = pkg_headers; - current_col = 0; - new_col = 1; - bintick_col = new_col + 1; - srctick_col = bintick_col + 1; - cat_col = srctick_col + 1; - size_col = cat_col + 1; - pkg_col = size_col + 1; - last_col = pkg_col; - } - else if (vm == views::Category) - { - headers = cat_headers; - cat_col = 0; - current_col = 1; - new_col = current_col + 1; - bintick_col = new_col + 1; - srctick_col = bintick_col + 1; - size_col = srctick_col + 1; - pkg_col = size_col + 1; - last_col = pkg_col; - } - else - return -1; - return last_col; -} - -void -PickView::set_headers () -{ - if (set_header_column_order (view_mode) == -1) - return; - while (int n = SendMessage (listheader, HDM_GETITEMCOUNT, 0, 0)) - { - SendMessage (listheader, HDM_DELETEITEM, n - 1, 0); - } - int i; - for (i = 0; i <= last_col; i++) - DoInsertItem (listheader, i, headers[i].width, (char *) headers[i].text); -} - -void -PickView::note_width (PickView::Header *hdrs, HDC dc, - const std::string& string, int addend, int column) -{ - SIZE s = { 0, 0 }; - - if (string.size()) - GetTextExtentPoint32 (dc, string.c_str(), string.size(), &s); - if (hdrs[column].width < s.cx + addend) - hdrs[column].width = s.cx + addend; -} - void PickView::setViewMode (views mode) { view_mode = mode; - set_headers (); packagedb db; - contents.empty (); + size_t i; + for (i = 0; i < contents.size(); i++) + { + delete contents[i]; + } + contents.clear(); + if (view_mode == PickView::views::Category) { - contents.ShowLabel (true); - /* start collapsed. TODO: make this a chooser flag */ - for (packagedb::categoriesType::iterator n = - packagedb::categories.begin(); n != packagedb::categories.end(); - ++n) - insert_category (&*n, (*n).first.c_str()[0] == '.' - ? CATEGORY_EXPANDED : CATEGORY_COLLAPSED); + insert_category (cat_tree_root); } else { - contents.ShowLabel (false); // iterate through every package for (packagedb::packagecollection::iterator i = db.packages.begin (); i != db.packages.end (); ++i) @@ -175,7 +65,7 @@ PickView::setViewMode (views mode) (pkg.desired && (pkg.picked () || // install bin pkg.srcpicked ())))) // src - + // "Up to date" : installed packages that will not be changed || (view_mode == PickView::views::PackageKeeps && (pkg.installed && pkg.desired && !pkg.picked () @@ -191,29 +81,13 @@ PickView::setViewMode (views mode) { // Filter by package name if (packageFilterString.empty () - || StrStrI (pkg.name.c_str (), packageFilterString.c_str ())) + || StrStrI (pkg.name.c_str (), packageFilterString.c_str ())) insert_pkg (pkg); } } } - RECT r = GetClientRect (); - SCROLLINFO si; - memset (&si, 0, sizeof (si)); - si.cbSize = sizeof (si); - si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; - si.nMin = 0; - si.nMax = headers[last_col].x + headers[last_col].width; // + HMARGIN; - si.nPage = r.right; - SetScrollInfo (GetHWND(), SB_HORZ, &si, TRUE); - - si.nMax = contents.itemcount () * row_height; - si.nPage = r.bottom - header_height; - SetScrollInfo (GetHWND(), SB_VERT, &si, TRUE); - - scroll_ulc_x = scroll_ulc_y = 0; - - InvalidateRect (GetHWND(), &r, TRUE); + listview->setContents(&contents, view_mode == PickView::views::Category); } PickView::views @@ -259,7 +133,7 @@ isObsolete (std::set &categories) bool isObsolete (const std::string& catname) { - if (casecompare(catname, "ZZZRemovedPackages") == 0 + if (casecompare(catname, "ZZZRemovedPackages") == 0 || casecompare(catname, "_", 1) == 0) return true; return false; @@ -273,120 +147,84 @@ PickView::setObsolete (bool doit) refresh (); } - void -PickView::insert_pkg (packagemeta & pkg) +PickView::insert_pkg (packagemeta & pkg, int indent) { if (!showObsolete && isObsolete (pkg.categories)) return; - PickLine & line = *new PickPackageLine (*this, pkg); - contents.insert (line); + contents.push_back(new PickPackageLine(*this, pkg, indent)); } void -PickView::insert_category (Category *cat, bool collapsed) +PickView::insert_category (CategoryTree *cat_tree) { - // Urk, special case - if (casecompare(cat->first, "All") == 0 || - (!showObsolete && isObsolete (cat->first))) + if (!cat_tree) return; - PickCategoryLine & catline = *new PickCategoryLine (*this, *cat, 1, collapsed); + + const Category *cat = &(cat_tree->category()); + + // Suppress obsolete category when not showing obsolete + if ((!showObsolete && isObsolete (cat->first))) + return; + + // if it's not the "All" category int packageCount = 0; - for (std::vector ::iterator i = cat->second.begin (); - i != cat->second.end () ; ++i) + bool hasContents = false; + bool isAll = casecompare(cat->first, "All") == 0; + if (!isAll) { - if (packageFilterString.empty () \ - || (*i - && StrStrI ((*i)->name.c_str (), packageFilterString.c_str ()))) - { - PickLine & line = *new PickPackageLine (*this, **i); - catline.insert (line); - packageCount++; - } - } - - if (packageFilterString.empty () || packageCount) - contents.insert (catline); - else - delete &catline; -} + // count the number of packages in this category + for (std::vector ::const_iterator i = cat->second.begin (); + i != cat->second.end () ; ++i) + { + if (packageFilterString.empty () \ + || (*i + && StrStrI ((*i)->name.c_str (), packageFilterString.c_str ()))) + { + packageCount++; + } + } -int -PickView::click (int row, int x) -{ - return contents.click (0, row, x); -} + // if there are some packages in the category, or we are showing everything, + if (packageFilterString.empty () || packageCount) + { + hasContents = true; + } + } + if (!isAll && !hasContents) + return; -void -PickView::scroll (HWND hwnd, int which, int *var, int code, int howmany = 1) -{ - SCROLLINFO si; - si.cbSize = sizeof (si); - si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; - GetScrollInfo (hwnd, which, &si); + // insert line for the category + contents.push_back(new PickCategoryLine(*this, cat_tree, packageCount, isAll ? 0 : 1)); - switch (code) + // if not collapsed + if (!cat_tree->collapsed()) { - case SB_THUMBTRACK: - si.nPos = si.nTrackPos; - break; - case SB_THUMBPOSITION: - break; - case SB_BOTTOM: - si.nPos = si.nMax; - break; - case SB_TOP: - si.nPos = 0; - break; - case SB_LINEDOWN: - si.nPos += (row_height * howmany); - break; - case SB_LINEUP: - si.nPos -= (row_height * howmany); - break; - case SB_PAGEDOWN: - si.nPos += si.nPage * 9 / 10; - break; - case SB_PAGEUP: - si.nPos -= si.nPage * 9 / 10; - break; - } + // insert lines for the packages in this category + if (!isAll) + { + for (std::vector ::const_iterator i = cat->second.begin (); + i != cat->second.end () ; ++i) + { + if (packageFilterString.empty () \ + || (*i + && StrStrI ((*i)->name.c_str (), packageFilterString.c_str ()))) + { + insert_pkg(**i, 2); + } + } + } - if ((int) si.nPos < 0) - si.nPos = 0; - if (si.nPos + si.nPage > (unsigned int) si.nMax) - si.nPos = si.nMax - si.nPage; - - si.fMask = SIF_POS; - SetScrollInfo (hwnd, which, &si, TRUE); - - int ox = scroll_ulc_x; - int oy = scroll_ulc_y; - *var = si.nPos; - - RECT cr, sr; - ::GetClientRect (hwnd, &cr); - sr = cr; - sr.top += header_height; - UpdateWindow (hwnd); - ScrollWindow (hwnd, ox - scroll_ulc_x, oy - scroll_ulc_y, &sr, &sr); - /* - sr.bottom = sr.top; - sr.top = cr.top; - ScrollWindow (hwnd, ox - scroll_ulc_x, 0, &sr, &sr); - */ - if (ox - scroll_ulc_x) - { - ::GetClientRect (listheader, &cr); - sr = cr; -// UpdateWindow (htmp); - ::MoveWindow (listheader, -scroll_ulc_x, 0, - headers[last_col].x + - headers[last_col].width, header_height, TRUE); + // recurse for contained categories + for (std::vector ::iterator i = cat_tree->bucket().begin (); + i != cat_tree->bucket().end(); + i++) + { + insert_category(*i); + } } - UpdateWindow (hwnd); } /* this means to make the 'category' column wide enough to fit the first 'n' @@ -394,72 +232,56 @@ PickView::scroll (HWND hwnd, int which, int *var, int code, int howmany = 1) #define NUM_CATEGORY_COL_WIDTH 2 void -PickView::init_headers (HDC dc) +PickView::init_headers (void) { - int i; + listview->noteColumnWidthStart(); - for (i = 0; headers[i].text; i++) - { - headers[i].width = 0; - headers[i].x = 0; - } - - // A margin of 3*GetSystemMetrics(SM_CXEDGE) is used at each side of the - // header text. (Probably should use that rather than hard-coding HMARGIN - // everywhere) - int addend = 2*3*GetSystemMetrics(SM_CXEDGE); + // widths of the 'bin' and 'src' checkbox columns just need to accommodate the + // column name + listview->noteColumnWidth (bintick_col, ""); + listview->noteColumnWidth (srctick_col, ""); - // accommodate widths of the 'bin' and 'src' checkbox columns - note_width (headers, dc, headers[bintick_col].text, addend, bintick_col); - note_width (headers, dc, headers[srctick_col].text, addend, srctick_col); - - // accomodate the width of each category name + // (In category view) accommodate the width of each category name packagedb db; for (packagedb::categoriesType::iterator n = packagedb::categories.begin(); n != packagedb::categories.end(); ++n) { - if (!showObsolete && isObsolete (n->first)) - continue; - note_width (headers, dc, n->first, HMARGIN, cat_col); + listview->noteColumnWidth (cat_col, n->first); } /* For each package, accomodate the width of the installed version in the - current_col, the widths of all other versions in the new_col, and the - width of the sdesc for the pkg_col. Also, if this is not a Category - view, adjust the 'category' column so that the first NUM_CATEGORY_COL_WIDTH - categories from each package fits. */ + current_col, the widths of all other versions in the new_col, and the width + of the sdesc for the pkg_col and the first NUM_CATEGORY_COL_WIDTH + categories in the category column. */ for (packagedb::packagecollection::iterator n = db.packages.begin (); n != db.packages.end (); ++n) { packagemeta & pkg = *(n->second); - if (!showObsolete && isObsolete (pkg.categories)) - continue; if (pkg.installed) - note_width (headers, dc, pkg.installed.Canonical_version (), - HMARGIN, current_col); + listview->noteColumnWidth (current_col, pkg.installed.Canonical_version ()); for (std::set::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::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 = ℘ - if (!SendMessage (listheader, HDM_LAYOUT, 0, (LPARAM) & hdl)) - throw new Exception (TOSTRING(__LINE__) " " __FILE__, - "Unable to get size and position of rectangle", - APPERR_WINDOW_ERROR); - - // Set the font of the listheader, but don't redraw, because its not shown - // yet.This message does not return a value, so we are not checking it as we - // do above. - SendMessage (listheader, WM_SETFONT, (WPARAM) sysfont, FALSE); - - // Set the size, position, and visibility of the header control. - SetWindowPos (listheader, wp.hwndInsertAfter, wp.x, wp.y, - wp.cx, wp.cy, wp.flags | SWP_SHOWWINDOW); - - header_height = wp.cy; - ReleaseDC (GetHWND (), dc); - view_mode = _mode; - refresh (); -} - -PickView::~PickView() -{ - DeleteDC (bitmap_dc); - DeleteObject (bm_spin); - DeleteObject (bm_checkyes); - DeleteObject (bm_checkno); - DeleteObject (bm_checkna); - DeleteObject (bm_treeplus); - DeleteObject (bm_treeminus); - DeleteObject (rect_icon); - DeleteObject (bm_icon); - DeleteDC (icon_dc); -} - -bool PickView::registerWindowClass () -{ - if (WindowClassAtom != 0) - return true; - - // We're not registered yet - WNDCLASSEX wc; - - wc.cbSize = sizeof (wc); - // Some sensible style defaults - wc.style = CS_HREDRAW | CS_VREDRAW; - // Our default window procedure. This replaces itself - // on the first call with the simpler Window::WindowProcReflector(). - wc.lpfnWndProc = Window::FirstWindowProcReflector; - // No class bytes - wc.cbClsExtra = 0; - // One pointer to REFLECTION_INFO in the extra window instance bytes - wc.cbWndExtra = 4; - // The app instance - wc.hInstance = hinstance; //GetInstance (); - // Use a bunch of system defaults for the GUI elements - wc.hIcon = LoadIcon (0, IDI_APPLICATION); - wc.hIconSm = NULL; - wc.hCursor = LoadCursor (0, IDC_ARROW); - wc.hbrBackground = NULL; - // No menu - wc.lpszMenuName = NULL; - // We'll get a little crazy here with the class name - wc.lpszClassName = "listview"; - - // All set, try to register - WindowClassAtom = RegisterClassEx (&wc); - if (WindowClassAtom == 0) - Log (LOG_BABBLE) << "Failed to register listview " << GetLastError () << endLog; - return WindowClassAtom != 0; -} - -LRESULT CALLBACK -PickView::list_vscroll (HWND hwnd, HWND hctl, UINT code, int pos) -{ - scroll (hwnd, SB_VERT, &scroll_ulc_y, code); - return 0; -} - -LRESULT CALLBACK -PickView::list_hscroll (HWND hwnd, HWND hctl, UINT code, int pos) -{ - scroll (hwnd, SB_HORZ, &scroll_ulc_x, code); - return 0; + listview = _listview; + parent = _parent; } void -PickView::set_vscroll_info (const RECT &r) -{ - SCROLLINFO si; - memset (&si, 0, sizeof (si)); - si.cbSize = sizeof (si); - si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; /* SIF_RANGE was giving strange behaviour */ - si.nMin = 0; - - si.nMax = contents.itemcount () * row_height; - si.nPage = r.bottom - header_height; - - /* if we are under the minimum display count , - * set the offset to 0 - */ - if ((unsigned int) si.nMax <= si.nPage) - scroll_ulc_y = 0; - si.nPos = scroll_ulc_y; - - SetScrollInfo (GetHWND(), SB_VERT, &si, TRUE); -} - -LRESULT CALLBACK -PickView::list_click (HWND hwnd, BOOL dblclk, int x, int y, UINT hitCode) +PickView::build_category_tree() { - int row, refresh __attribute__ ((unused)); - - if (contents.itemcount () == 0) - return 0; - - if (y < header_height) - return 0; - x += scroll_ulc_x; - y += scroll_ulc_y - header_height; - - row = (y + ROW_MARGIN / 2) / row_height; - - if (row < 0 || row >= contents.itemcount ()) - return 0; + /* Build the category tree */ - refresh = click (row, x); + /* Start collapsed. TODO: make that a flag */ + bool collapsed = true; - // XXX we need a method to query the database to see if more - // than just one package has changed! Until then... -#if 0 - if (refresh) + /* Dispose of any existing category tree */ + if (cat_tree_root) { -#endif - RECT r = GetClientRect (); - set_vscroll_info (r); - InvalidateRect (GetHWND(), &r, TRUE); -#if 0 - } - else - { - RECT rect; - rect.left = - headers[new_col].x - scroll_ulc_x; - rect.right = - headers[src_col + 1].x - scroll_ulc_x; - rect.top = - header_height + row * row_height - - scroll_ulc_y; - rect.bottom = rect.top + row_height; - InvalidateRect (hwnd, &rect, TRUE); + for (std::vector ::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((Window *)this)); - if (GetHWND() == NULL) - { - Log (LOG_BABBLE) << "Failed to create PickView " << GetLastError () << endLog; - return false; - } - - return true; } void PickView::defaultTrust (trusts trust) { this->deftrust = trust; - - // force the picker to redraw - RECT r = GetClientRect (); - InvalidateRect (this->GetHWND(), &r, TRUE); } -/* This recalculates all column widths and resets the view */ void PickView::refresh() { - HDC dc = GetDC (GetHWND ()); - - // we must set the font of the DC here, otherwise the width calculations - // will be off because the system will use the wrong font metrics - sysfont = GetStockObject (DEFAULT_GUI_FONT); - SelectObject (dc, sysfont); - - // init headers for the current mode - set_headers (); - init_headers (dc); - - // save the current mode - views cur_view_mode = view_mode; - - // switch to the other type and do those headers - view_mode = (view_mode == PickView::views::Category) ? - PickView::views::PackageFull : PickView::views::Category; - set_headers (); - init_headers (dc); - ReleaseDC (GetHWND (), dc); - - view_mode = cur_view_mode; setViewMode (view_mode); } diff --git a/PickView.h b/PickView.h index 298f844..3a6c602 100644 --- a/PickView.h +++ b/PickView.h @@ -17,84 +17,17 @@ #define SETUP_PICKVIEW_H #include -#include "win32.h" -#include "window.h" -#include "RECTWrapper.h" - -#define HMARGIN 10 -#define ROW_MARGIN 5 -#define ICON_MARGIN 4 -#define SPIN_WIDTH 11 -#define CHECK_SIZE 11 -#define TREE_INDENT 12 - -#define CATEGORY_EXPANDED 0 -#define CATEGORY_COLLAPSED 1 - -class PickView; -#include "PickCategoryLine.h" + #include "package_meta.h" +#include "ListView.h" + +class Window; +class CategoryTree; -class PickView : public Window +class PickView { public: - virtual bool Create (Window * Parent = NULL, DWORD Style = WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, RECT * r = NULL); - virtual bool registerWindowClass (); - enum class views; - class Header; - int num_columns; - void defaultTrust (trusts trust); - void setViewMode (views mode); - views getViewMode (); - void DrawIcon (HDC hdc, int x, int y, HANDLE hIcon); - void paint (HWND hwnd); - LRESULT CALLBACK list_click (HWND hwnd, BOOL dblclk, int x, int y, UINT hitCode); - LRESULT CALLBACK list_hscroll (HWND hwnd, HWND hctl, UINT code, int pos); - LRESULT CALLBACK list_vscroll (HWND hwnd, HWND hctl, UINT code, int pos); - void set_vscroll_info (const RECT &r); - virtual LRESULT WindowProc (UINT uMsg, WPARAM wParam, LPARAM lParam); - Header *headers; - PickView (Category & cat); - void init(views _mode); - ~PickView(); - static const char *mode_caption (views mode); - void setObsolete (bool doit); - void insert_pkg (packagemeta &); - void insert_category (Category *, bool); - int click (int row, int x); - void refresh(); - int current_col; - int new_col; - int bintick_col; - int srctick_col; - int cat_col; - int size_col; - int pkg_col; - int last_col; - int row_height; - TEXTMETRIC tm; - HDC bitmap_dc, icon_dc; - HBITMAP bm_icon; - HRGN rect_icon; - HBRUSH bg_fg_brush; - HANDLE bm_spin, bm_checkyes, bm_checkno, bm_checkna, bm_treeplus, bm_treeminus; - trusts deftrust; - HANDLE sysfont; - int scroll_ulc_x, scroll_ulc_y; - int header_height; - PickCategoryLine contents; - void scroll (HWND hwnd, int which, int *var, int code, int howmany); - - void SetPackageFilter (const std::string &filterString) - { - packageFilterString = filterString; - } - - - HWND ListHeader (void) const - { - return listheader; - } + trusts deftrust; // XXX: needs accessor enum class views { @@ -106,35 +39,135 @@ public: Category, }; - class Header + PickView (); + ~PickView(); + void defaultTrust (trusts trust); + void setViewMode (views mode); + views getViewMode (); + void init(views _mode, ListView *_listview, Window *parent); + void build_category_tree(); + static const char *mode_caption (views mode); + void setObsolete (bool doit); + void refresh(); + void init_headers (); + + void SetPackageFilter (const std::string &filterString) { - public: - const char *text; - int width; - int x; - bool needs_clip; - }; + packageFilterString = filterString; + } + + Window *GetParent(void) { return parent; } private: - static ATOM WindowClassAtom; - HWND listheader; views view_mode; + ListView *listview; bool showObsolete; std::string packageFilterString; + ListViewContents contents; + CategoryTree *cat_tree_root; + Window *parent; - // Stuff needed to handle resizing - bool hasWindowRect; - RECTWrapper lastWindowRect; - int total_delta_x; + void insert_pkg (packagemeta &, int indent = 0); + void insert_category (CategoryTree *); +}; - int set_header_column_order (views vm); - void set_headers (); - void init_headers (HDC dc); - void note_width (Header *hdrs, HDC dc, const std::string& string, - int addend, int column); +enum +{ + pkgname_col = 0, // package/category name + current_col = 1, + new_col = 2, // action + bintick_col = 3, + srctick_col = 4, + cat_col = 5, + size_col = 6, + pkg_col = 7, // desc }; bool isObsolete (std::set &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 & 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 ::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 ::const_iterator pkg = _cat.second.begin(); + pkg != _cat.second.end(); + ++pkg) + { + (*pkg)->select_action(action_id, deftrust); + l++; + } + + // these lines need to be updated, if displayed + if (!_collapsed) + u += l; + } + return u; + } + +private: + Category & _cat; + bool _collapsed; + std::vector _bucket; + packagemeta::_actions _action; +}; + #endif /* SETUP_PICKVIEW_H */ diff --git a/check-na.bmp b/check-na.bmp deleted file mode 100644 index c139e54..0000000 Binary files a/check-na.bmp and /dev/null differ diff --git a/check-no.bmp b/check-no.bmp deleted file mode 100644 index 3639605..0000000 Binary files a/check-no.bmp and /dev/null differ diff --git a/check-yes.bmp b/check-yes.bmp deleted file mode 100644 index f328dc2..0000000 Binary files a/check-yes.bmp and /dev/null differ diff --git a/choose-spin.bmp b/choose-spin.bmp deleted file mode 100644 index 8779f6d..0000000 Binary files a/choose-spin.bmp and /dev/null differ diff --git a/choose.cc b/choose.cc index 98671c1..2b5e465 100644 --- a/choose.cc +++ b/choose.cc @@ -79,7 +79,6 @@ static ControlAdjuster::ControlInfo ChooserControlsInfo[] = { {IDC_CHOOSE_SYNC, CP_RIGHT, CP_TOP}, {IDC_CHOOSE_EXP, CP_RIGHT, CP_TOP}, {IDC_CHOOSE_VIEW, CP_LEFT, CP_TOP}, - {IDC_LISTVIEW_POS, CP_RIGHT, CP_TOP}, {IDC_CHOOSE_VIEWCAPTION, CP_LEFT, CP_TOP}, {IDC_CHOOSE_LIST, CP_STRETCH, CP_STRETCH}, {IDC_CHOOSE_HIDE, CP_LEFT, CP_BOTTOM}, @@ -130,23 +129,33 @@ ChooserPage::~ChooserPage () } } +static ListView::Header pkg_headers[] = { + {"Package", LVCFMT_LEFT, ListView::ControlType::text}, + {"Current", LVCFMT_LEFT, ListView::ControlType::text}, + {"New", LVCFMT_LEFT, ListView::ControlType::popup}, + {"Bin?", LVCFMT_LEFT, ListView::ControlType::checkbox}, + {"Src?", LVCFMT_LEFT, ListView::ControlType::checkbox}, + {"Categories", LVCFMT_LEFT, ListView::ControlType::text}, + {"Size", LVCFMT_RIGHT, ListView::ControlType::text}, + {"Description", LVCFMT_LEFT, ListView::ControlType::text}, + {0} +}; + void ChooserPage::createListview () { SetBusy (); - static std::vector empty_cat; - static Category dummy_cat (std::string ("All"), empty_cat); - chooser = new PickView (dummy_cat); - RECT r = getDefaultListViewSize(); - if (!chooser->Create(this, WS_CHILD | WS_HSCROLL | WS_VSCROLL | WS_VISIBLE,&r)) - throw new Exception (TOSTRING(__LINE__) " " __FILE__, - "Unable to create chooser list window", - APPERR_WINDOW_ERROR); - chooser->init(PickView::views::Category); - chooser->Show(SW_SHOW); + + listview = new ListView(); + listview->init(GetHWND (), IDC_CHOOSE_LIST, pkg_headers); + + chooser = new PickView(); + chooser->init(PickView::views::Category, listview, this); chooser->setViewMode (!is_new_install || UpgradeAlsoOption || hasManualSelections ? - PickView::views::PackagePending : PickView::views::Category); + PickView::views::PackagePending : PickView::views::Category); + SendMessage (GetDlgItem (IDC_CHOOSE_VIEW), CB_SETCURSEL, (WPARAM)chooser->getViewMode(), 0); + ClearBusy (); } @@ -238,16 +247,6 @@ ChooserPage::setPrompt(char const *aString) ::SetWindowText (GetDlgItem (IDC_CHOOSE_INST_TEXT), aString); } -RECT -ChooserPage::getDefaultListViewSize() -{ - RECT result; - getParentRect (GetHWND (), GetDlgItem (IDC_LISTVIEW_POS), &result); - result.top += 2; - result.bottom -= 2; - return result; -} - void ChooserPage::OnInit () { @@ -334,6 +333,17 @@ ChooserPage::OnActivate() activated = true; } + packagedb::categoriesType::iterator it = db.categories.find("All"); + if (it == db.categories.end ()) + listview->setEmptyText("No packages found."); + if (source == IDC_SOURCE_DOWNLOAD) + listview->setEmptyText("Nothing to download."); + else + listview->setEmptyText("Nothing to install or update."); + + chooser->build_category_tree(); + chooser->init_headers(); + ClearBusy(); chooser->refresh(); @@ -445,7 +455,7 @@ bool ChooserPage::OnMessageCmd (int id, HWND hwndctl, UINT code) { #if DEBUG - Log (LOG_BABBLE) << "OnMessageCmd " << id << " " << hwndctl << " " << std::hex << code << endLog; + Log (LOG_BABBLE) << "ChooserPage::OnMessageCmd " << id << " " << hwndctl << " " << std::hex << code << endLog; #endif if (id == IDC_CHOOSE_SEARCH_EDIT) @@ -547,10 +557,19 @@ ChooserPage::OnMessageCmd (int id, HWND hwndctl, UINT code) return false; } -INT_PTR CALLBACK -ChooserPage::OnMouseWheel (UINT message, WPARAM wParam, LPARAM lParam) +bool +ChooserPage::OnNotify (NMHDR *pNmHdr, LRESULT *pResult) { - return chooser->WindowProc (message, wParam, lParam); +#if DEBUG + Log (LOG_BABBLE) << "ChooserPage::OnNotify id:" << pNmHdr->idFrom << " hwnd:" << pNmHdr->hwndFrom << " code:" << (int)pNmHdr->code << endLog; +#endif + + // offer messages to the listview + if (listview->OnNotify(pNmHdr, pResult)) + return true; + + // we don't care + return false; } INT_PTR CALLBACK diff --git a/choose.h b/choose.h index 32a1650..e4953b7 100644 --- a/choose.h +++ b/choose.h @@ -20,6 +20,7 @@ #include "proppage.h" #include "package_meta.h" #include "PickView.h" +#include "ListView.h" #define DEFAULT_TIMER_ID 5 //value doesn't matter, as long as it's unique #define SEARCH_TIMER_DELAY 500 //in milliseconds @@ -33,8 +34,7 @@ public: ~ChooserPage (); virtual bool OnMessageCmd (int id, HWND hwndctl, UINT code); - virtual INT_PTR CALLBACK OnMouseWheel (UINT message, WPARAM wParam, - LPARAM lParam); + virtual bool OnNotify (NMHDR *pNmHdr, LRESULT *pResult); virtual INT_PTR CALLBACK OnTimerMessage (UINT message, WPARAM wParam, LPARAM lparam); @@ -51,7 +51,6 @@ public: } private: void createListview (); - RECT getDefaultListViewSize(); void getParentRect (HWND parent, HWND child, RECT * r); void keepClicked(); void changeTrust(int button, bool test, bool initial); @@ -63,6 +62,7 @@ private: void initialUpdateState(); PickView *chooser; + ListView *listview; static HWND ins_dialog; bool cmd_show_set; bool saved_geom; diff --git a/libsolv.cc b/libsolv.cc index 955a1b2..ba54fc5 100644 --- a/libsolv.cc +++ b/libsolv.cc @@ -193,6 +193,20 @@ SolvableVersion::SDesc () const } const std::string +SolvableVersion::LDesc () const +{ + if (!id) + return ""; + Solvable *solvable = pool_id2solvable(pool, id); + const char *ldesc = repo_lookup_str(solvable->repo, id, SOLVABLE_DESCRIPTION); + + if (!ldesc) + return ""; + + return ldesc; +} + +const std::string SolvableVersion::sourcePackageName () const { if (!id) diff --git a/libsolv.h b/libsolv.h index f394e65..2eb1f24 100644 --- a/libsolv.h +++ b/libsolv.h @@ -54,6 +54,7 @@ class SolvableVersion const std::string Name () const; const std::string SDesc () const; + const std::string LDesc () const; // In setup-speak, 'Canonical' version means 'e:v-r', the non-decomposed version const std::string Canonical_version () const; // Return the dependency list diff --git a/main.cc b/main.cc index caa213f..749b00c 100644 --- a/main.cc +++ b/main.cc @@ -143,7 +143,7 @@ main_display () // Initialize common controls INITCOMMONCONTROLSEX icce = { sizeof (INITCOMMONCONTROLSEX), - ICC_WIN95_CLASSES }; + ICC_WIN95_CLASSES | ICC_LISTVIEW_CLASSES }; InitCommonControlsEx (&icce); // Initialize COM and ShellLink instance here. For some reason diff --git a/package_db.cc b/package_db.cc index 8b8e120..59c59ef 100644 --- a/package_db.cc +++ b/package_db.cc @@ -147,6 +147,7 @@ packagedb::read () if (pv) { data.sdesc = pv.SDesc(); + data.ldesc = pv.LDesc(); data.archive = *pv.source(); data.stability = pv.Stability(); data.spkg_id = pv.sourcePackage(); @@ -170,6 +171,7 @@ packagedb::read () if (pkgm) { data.sdesc = pkgm->curr.SDesc(); + data.ldesc = pkgm->curr.LDesc(); if (pkgm->curr && version_compare (f.ver, pkgm->curr.Canonical_version()) > 0) data.stability = TRUST_TEST; diff --git a/package_meta.cc b/package_meta.cc index ab1e175..9880bcb 100644 --- a/package_meta.cc +++ b/package_meta.cc @@ -51,35 +51,22 @@ bool hasManualSelections = 0; /*****************/ -const - packagemeta::_actions -packagemeta::Default_action (0); -const - packagemeta::_actions -packagemeta::Install_action (1); -const - packagemeta::_actions -packagemeta::Reinstall_action (2); -const - packagemeta::_actions -packagemeta::Uninstall_action (3); - char const * -packagemeta::_actions::caption () +packagemeta::action_caption (_actions _value) { switch (_value) { - case 0: + case Default_action: return "Default"; - case 1: + case Install_action: return "Install"; - case 2: + case Reinstall_action: return "Reinstall"; - case 3: + case Uninstall_action: return "Uninstall"; } - // Pacify GCC: (all case options are checked above) - return 0; + + return "Unknown"; } packagemeta::packagemeta (packagemeta const &rhs) : @@ -93,14 +80,6 @@ packagemeta::packagemeta (packagemeta const &rhs) : } -packagemeta::_actions & packagemeta::_actions::operator++ () -{ - ++_value; - if (_value > 3) - _value = 0; - return *this; -} - template struct removeCategory : public std::unary_function { removeCategory(packagemeta *pkg) : _pkg (pkg) {} @@ -427,6 +406,21 @@ packagemeta::SDesc () const return std::string(); } +static bool +hasLDesc(packageversion const &pkg) +{ + return pkg.LDesc().size(); +} + +const std::string +packagemeta::LDesc () const +{ + std::set::iterator i = find_if (versions.begin(), versions.end(), hasLDesc); + if (i == versions.end()) + return std::string(); + return i->LDesc (); +}; + /* Return an appropriate caption given the current action. */ std::string packagemeta::action_caption () const @@ -446,63 +440,24 @@ packagemeta::action_caption () const return desired.Canonical_version (); } -/* Set the next action given a current action. */ void -packagemeta::set_action (trusts const trust) +packagemeta::select_action (int id, trusts const deftrust) { - std::set::iterator i; - - /* Keep the picked settings of the former desired version, if any, and make - sure at least one of them is picked. If both are unpicked, pick the - binary version. */ - bool source_picked = desired && srcpicked (); - bool binary_picked = !desired || picked () || !source_picked; - - /* If we're on "Keep" on the installed version, and the version is available, - switch to "Reinstall". */ - if (desired && desired == installed && !picked () - && desired.accessible ()) + if (id <= 0) { - pick (true); - return; - } + // Install a specific version + std::set::iterator i = versions.begin (); + for (int j = -id; j > 0; j--) + i++; - if (!desired) - { - /* From "Uninstall" switch to the first version. From "Skip" switch to - the first version as well, unless the user picks for the first time. - In that case switch to the trustp version immediately. */ - if (installed || user_picked) - i = versions.begin (); - else - for (i = versions.begin (); - i != versions.end () && *i != trustp (false, trust); - ++i) - ; - } - else - { - /* Otherwise switch to the next version. */ - for (i = versions.begin (); i != versions.end () && *i != desired; ++i) - ; - ++i; - } - /* If there's another version in the list, switch to it, otherwise - switch to "Uninstall". */ - if (i != versions.end ()) - { - desired = *i; - /* If the next version is the installed version, unpick it. This will - have the desired effect to show the package in "Keep" mode. See also - above for the code switching to "Reinstall". */ - pick (desired != installed && binary_picked); - srcpick (desired.sourcePackage().accessible () && source_picked); + set_action(Install_action, *i); } else { - desired = packageversion (); - pick(false); - srcpick(false); + if (id == packagemeta::Default_action) + set_action((packagemeta::_actions)id, installed); + else + set_action((packagemeta::_actions)id, trustp (true, deftrust)); } /* Memorize the fact that the user picked at least once. */ @@ -510,6 +465,50 @@ packagemeta::set_action (trusts const trust) user_picked = true; } +ActionList * +packagemeta::list_actions(trusts const trust) +{ + // first work out the current action, so we can indicate that + _actions action; + + if (!desired && installed) + action = Uninstall_action; + else if (!desired) + action = Default_action; // skip + else if (desired == installed && picked()) + action = Reinstall_action; + else if (desired == installed) + action = Default_action; // keep + else + action = Install_action; + + // now build the list of possible actions + ActionList *al = new ActionList(); + + al->add("Uninstall", (int)Uninstall_action, (action == Uninstall_action), bool(installed)); + al->add("Skip", (int)Default_action, (action == Default_action) && !installed, !installed); + + std::set::iterator i; + for (i = versions.begin (); i != versions.end (); ++i) + { + if (*i == installed) + { + al->add("Keep", (int)Default_action, (action == Default_action), TRUE); + al->add(packagedb::task == PackageDB_Install ? "Reinstall" : "Retrieve", + (int)Reinstall_action, (action == Reinstall_action), TRUE); + } + else + { + al->add(i->Canonical_version().c_str(), + -std::distance(versions.begin (), i), + (action == Install_action) && (*i == desired), + TRUE); + } + } + + return al; +} + // Set a particular type of action. void packagemeta::set_action (_actions action, packageversion const &default_version) diff --git a/package_meta.h b/package_meta.h index 8db10e2..0eff8d0 100644 --- a/package_meta.h +++ b/package_meta.h @@ -26,6 +26,7 @@ class packagemeta; #include "package_version.h" #include "package_message.h" #include "script.h" +#include "ActionList.h" typedef std::pair > Category; @@ -50,28 +51,18 @@ public: void setDefaultCategories(); void addToCategoryAll(); - class _actions - { - public: - _actions ():_value (0) {}; - _actions (int aInt) { - _value = aInt; - if (_value < 0 || _value > 3) - _value = 0; - } - _actions & operator ++ (); - bool operator == (_actions const &rhs) { return _value == rhs._value; } - bool operator != (_actions const &rhs) { return _value != rhs._value; } - const char *caption (); - private: - int _value; - }; - static const _actions Default_action; - static const _actions Install_action; - static const _actions Reinstall_action; - static const _actions Uninstall_action; - void set_action (trusts const t); + enum _actions + { + Default_action = 1, + Install_action, + Reinstall_action, + Uninstall_action, + }; + static const char *action_caption (_actions value); + void set_action (_actions, packageversion const & default_version); + ActionList *list_actions(trusts const trust); + void select_action (int id, trusts const deftrust); void set_message (const std::string& message_id, const std::string& message_string) { @@ -117,9 +108,10 @@ public: bool isManuallyWanted() const; /* true if package was deleted on command-line. */ bool isManuallyDeleted() const; - /* SDesc is global in theory, across all package versions. - LDesc is not: it can be different per version */ + const std::string SDesc () const; + const std::string LDesc () const; + /* what categories does this package belong in. Note that if multiple versions * of a package disagree.... the first one read in will take precedence. */ diff --git a/proppage.cc b/proppage.cc index 6b83640..8da1c52 100644 --- a/proppage.cc +++ b/proppage.cc @@ -140,7 +140,18 @@ PropertyPage::DialogProc (UINT message, WPARAM wParam, LPARAM lParam) return TRUE; } case WM_NOTIFY: - switch (((NMHDR FAR *) lParam)->code) + { + NMHDR *pNmHdr = (NMHDR *) lParam; + + // offer to subclass first + LRESULT result = 0; + if (OnNotify (pNmHdr, &result)) + { + SetWindowLongPtr (GetHWND (), DWLP_MSGRESULT, result); + return TRUE; + } + + switch (pNmHdr->code) { case PSN_APPLY: { @@ -261,6 +272,7 @@ PropertyPage::DialogProc (UINT message, WPARAM wParam, LPARAM lParam) return FALSE; } } + } break; case WM_COMMAND: { diff --git a/res.rc b/res.rc index 02b60cf..10f20ba 100644 --- a/res.rc +++ b/res.rc @@ -1,5 +1,6 @@ #include "resource.h" #include "windows.h" +#include "commctrl.h" #define SETUP_STANDARD_DIALOG_W 339 #define SETUP_STANDARD_DIALOG_H 179 @@ -357,8 +358,8 @@ BEGIN SETUP_EXP_X, 30, SETUP_KPCE_W, 14 CONTROL "", IDC_HEADSEPARATOR, "Static", SS_BLACKFRAME | SS_SUNKEN, 0, 28, SETUP_STANDARD_DIALOG_W, 1 - CONTROL "", IDC_LISTVIEW_POS, "Static", SS_BLACKFRAME | NOT - WS_VISIBLE, 7, 45, SETUP_STANDARD_DIALOG_W - 14, 122 + CONTROL "", IDC_CHOOSE_LIST, WC_LISTVIEW, LVS_NOSORTHEADER | LVS_REPORT | LVS_SINGLESEL, + 7, 47, SETUP_STANDARD_DIALOG_W - 14, 120, WS_EX_CLIENTEDGE CONTROL "&Hide obsolete packages", IDC_CHOOSE_HIDE, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 7, 167, 160, 14 ICON IDI_CYGWIN, IDC_HEADICON, SETUP_HEADICON_X, 0, 21, 20 @@ -518,6 +519,8 @@ CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "setup.exe.manifest" IDI_CYGWIN_SETUP ICON DISCARDABLE "cygwin-setup.ico" IDI_CYGWIN ICON DISCARDABLE "cygwin.ico" IDI_CYGWIN_TERMINAL ICON DISCARDABLE "cygwin-terminal.ico" +IDI_TREE_PLUS ICON DISCARDABLE "tree-plus.ico" +IDI_TREE_MINUS ICON DISCARDABLE "tree-minus.ico" ///////////////////////////////////////////////////////////////////////////// // @@ -530,18 +533,6 @@ CYGWIN-TERMINAL.ICON FILE DISCARDABLE "cygwin-terminal.ico" ///////////////////////////////////////////////////////////////////////////// // -// Bitmap -// - -IDB_SPIN BITMAP DISCARDABLE "choose-spin.bmp" -IDB_CHECK_YES BITMAP DISCARDABLE "check-yes.bmp" -IDB_CHECK_NO BITMAP DISCARDABLE "check-no.bmp" -IDB_CHECK_NA BITMAP DISCARDABLE "check-na.bmp" -IDB_TREE_PLUS BITMAP DISCARDABLE "tree-plus.bmp" -IDB_TREE_MINUS BITMAP DISCARDABLE "tree-minus.bmp" - -///////////////////////////////////////////////////////////////////////////// -// // String Table // diff --git a/resource.h b/resource.h index 421a24c..852bdc0 100644 --- a/resource.h +++ b/resource.h @@ -71,20 +71,13 @@ #define IDD_DOWNLOAD_ERROR 224 #define IDD_CONFIRM 225 -// Bitmaps - -#define IDB_SPIN 300 -#define IDB_CHECK_YES 301 -#define IDB_CHECK_NO 302 -#define IDB_CHECK_NA 303 -#define IDB_TREE_PLUS 304 -#define IDB_TREE_MINUS 305 - // icons #define IDI_CYGWIN_SETUP 401 #define IDI_CYGWIN 402 #define IDI_CYGWIN_TERMINAL 403 +#define IDI_TREE_PLUS 404 +#define IDI_TREE_MINUS 405 // controls @@ -118,7 +111,6 @@ #define IDC_NET_USER 527 #define IDC_NET_PASSWD 528 #define IDC_VERSION 529 -#define IDC_LISTVIEW_POS 530 #define IDC_CHOOSE_VIEW 531 #define IDC_CHOOSE_EXP 532 #define IDC_CHOOSE_BEST 533 @@ -135,7 +127,6 @@ #define IDC_DLS_IPROGRESS_TEXT 545 #define IDC_CHOOSE_INST_TEXT 546 #define IDC_CHOOSE_VIEWCAPTION 547 -#define IDC_CHOOSE_LISTHEADER 548 #define IDC_INS_BL_PACKAGE 549 #define IDC_INS_BL_TOTAL 550 #define IDC_INS_BL_DISK 551 diff --git a/tree-minus.bmp b/tree-minus.bmp deleted file mode 100644 index 35b2221..0000000 Binary files a/tree-minus.bmp and /dev/null differ diff --git a/tree-minus.ico b/tree-minus.ico new file mode 100644 index 0000000..46fd3b1 Binary files /dev/null and b/tree-minus.ico differ diff --git a/tree-minus.svg b/tree-minus.svg new file mode 100644 index 0000000..124918b --- /dev/null +++ b/tree-minus.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/tree-plus.bmp b/tree-plus.bmp deleted file mode 100644 index e0335d9..0000000 Binary files a/tree-plus.bmp and /dev/null differ diff --git a/tree-plus.ico b/tree-plus.ico new file mode 100644 index 0000000..8ee3d5f Binary files /dev/null and b/tree-plus.ico differ diff --git a/tree-plus.svg b/tree-plus.svg new file mode 100644 index 0000000..4d2eb3f --- /dev/null +++ b/tree-plus.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + 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;