Process Hacker
colmgr.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * tree new column manager
4  *
5  * Copyright (C) 2011 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include <phapp.h>
24 #include <extmgri.h>
25 #include <phplug.h>
26 #include <colmgr.h>
27 
28 typedef struct _PH_CM_SORT_CONTEXT
29 {
31  ULONG SubId;
32  PVOID Context;
33  PPH_CM_POST_SORT_FUNCTION PostSortFunction;
34  PH_SORT_ORDER SortOrder;
36 
38  _Out_ PPH_CM_MANAGER Manager,
39  _In_ HWND Handle,
40  _In_ ULONG MinId,
41  _In_ PPH_CM_POST_SORT_FUNCTION PostSortFunction
42  )
43 {
44  Manager->Handle = Handle;
45  Manager->MinId = MinId;
46  Manager->NextId = MinId;
47  Manager->PostSortFunction = PostSortFunction;
48  InitializeListHead(&Manager->ColumnListHead);
49  Manager->NotifyList = NULL;
50 }
51 
53  _In_ PPH_CM_MANAGER Manager
54  )
55 {
56  PLIST_ENTRY listEntry;
57  PPH_CM_COLUMN column;
58 
59  listEntry = Manager->ColumnListHead.Flink;
60 
61  while (listEntry != &Manager->ColumnListHead)
62  {
63  column = CONTAINING_RECORD(listEntry, PH_CM_COLUMN, ListEntry);
64  listEntry = listEntry->Flink;
65 
66  PhFree(column);
67  }
68 
69  if (Manager->NotifyList)
70  PhDereferenceObject(Manager->NotifyList);
71 }
72 
74  _Inout_ PPH_CM_MANAGER Manager,
75  _In_ PPH_TREENEW_COLUMN Column,
76  _In_ struct _PH_PLUGIN *Plugin,
77  _In_ ULONG SubId,
78  _In_opt_ PVOID Context,
79  _In_ PVOID SortFunction
80  )
81 {
82  PPH_CM_COLUMN column;
83  PH_TREENEW_COLUMN tnColumn;
84 
85  column = PhAllocate(sizeof(PH_CM_COLUMN));
86  memset(column, 0, sizeof(PH_CM_COLUMN));
87  column->Id = Manager->NextId++;
88  column->Plugin = Plugin;
89  column->SubId = SubId;
90  column->Context = Context;
91  column->SortFunction = SortFunction;
92  InsertTailList(&Manager->ColumnListHead, &column->ListEntry);
93 
94  memset(&tnColumn, 0, sizeof(PH_TREENEW_COLUMN));
95  tnColumn.Id = column->Id;
96  tnColumn.Context = column;
97  tnColumn.Visible = Column->Visible;
98  tnColumn.CustomDraw = Column->CustomDraw;
99  tnColumn.SortDescending = Column->SortDescending;
100  tnColumn.Text = Column->Text;
101  tnColumn.Width = Column->Width;
102  tnColumn.Alignment = Column->Alignment;
103  tnColumn.DisplayIndex = Column->Visible ? Column->DisplayIndex : -1;
104  tnColumn.TextFlags = Column->TextFlags;
105  TreeNew_AddColumn(Manager->Handle, &tnColumn);
106 
107  return column;
108 }
109 
111  _In_ PPH_CM_MANAGER Manager,
112  _In_ PPH_STRINGREF PluginName,
113  _In_ ULONG SubId
114  )
115 {
116  PLIST_ENTRY listEntry;
117  PPH_CM_COLUMN column;
118 
119  listEntry = Manager->ColumnListHead.Flink;
120 
121  while (listEntry != &Manager->ColumnListHead)
122  {
123  column = CONTAINING_RECORD(listEntry, PH_CM_COLUMN, ListEntry);
124 
125  if (column->SubId == SubId && PhEqualStringRef(PluginName, &column->Plugin->AppContext.AppName, FALSE))
126  return column;
127 
128  listEntry = listEntry->Flink;
129  }
130 
131  return NULL;
132 }
133 
135  _In_ PPH_CM_MANAGER Manager,
136  _In_ struct _PH_PLUGIN *Plugin
137  )
138 {
139  if (!Manager->NotifyList)
140  {
141  Manager->NotifyList = PhCreateList(8);
142  }
143  else
144  {
145  if (PhFindItemList(Manager->NotifyList, Plugin) != -1)
146  return;
147  }
148 
149  PhAddItemList(Manager->NotifyList, Plugin);
150 }
151 
153  _In_ HWND hwnd,
154  _In_ PH_TREENEW_MESSAGE Message,
155  _In_opt_ PVOID Parameter1,
156  _In_opt_ PVOID Parameter2,
157  _In_ PPH_CM_MANAGER Manager
158  )
159 {
160  PH_PLUGIN_TREENEW_MESSAGE pluginMessage;
161  PPH_PLUGIN plugin;
162 
163  if (Message == TreeNewDestroying)
164  return FALSE;
165 
166  switch (Message)
167  {
168  case TreeNewGetCellText:
169  {
170  PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1;
171  PH_TREENEW_COLUMN tnColumn;
172  PPH_CM_COLUMN column;
173 
174  if (getCellText->Id < Manager->MinId)
175  return FALSE;
176 
177  if (!TreeNew_GetColumn(hwnd, getCellText->Id, &tnColumn))
178  return FALSE;
179 
180  column = tnColumn.Context;
181  pluginMessage.SubId = column->SubId;
182  pluginMessage.Context = column->Context;
183  plugin = column->Plugin;
184  }
185  break;
186  case TreeNewCustomDraw:
187  {
188  PPH_TREENEW_CUSTOM_DRAW customDraw = Parameter1;
189  PPH_CM_COLUMN column;
190 
191  if (customDraw->Column->Id < Manager->MinId)
192  return FALSE;
193 
194  column = customDraw->Column->Context;
195  pluginMessage.SubId = column->SubId;
196  pluginMessage.Context = column->Context;
197  plugin = column->Plugin;
198  }
199  break;
201  {
202  PPH_TREENEW_COLUMN tlColumn = Parameter1;
203  PPH_CM_COLUMN column;
204 
205  if (tlColumn->Id < Manager->MinId)
206  return FALSE;
207 
208  column = tlColumn->Context;
209  pluginMessage.SubId = column->SubId;
210  pluginMessage.Context = column->Context;
211  plugin = column->Plugin;
212  }
213  break;
214  default:
215  {
216  // Some plugins want to be notified about all messages.
217  if (Manager->NotifyList)
218  {
219  ULONG i;
220 
221  for (i = 0; i < Manager->NotifyList->Count; i++)
222  {
223  plugin = Manager->NotifyList->Items[i];
224 
225  pluginMessage.TreeNewHandle = hwnd;
226  pluginMessage.Message = Message;
227  pluginMessage.Parameter1 = Parameter1;
228  pluginMessage.Parameter2 = Parameter2;
229  pluginMessage.SubId = 0;
230  pluginMessage.Context = NULL;
231 
233  }
234  }
235  }
236  return FALSE;
237  }
238 
239  pluginMessage.TreeNewHandle = hwnd;
240  pluginMessage.Message = Message;
241  pluginMessage.Parameter1 = Parameter1;
242  pluginMessage.Parameter2 = Parameter2;
243 
245 
246  return TRUE;
247 }
248 
249 static int __cdecl PhCmpSortFunction(
250  _In_ void *context,
251  _In_ const void *elem1,
252  _In_ const void *elem2
253  )
254 {
255  PPH_CM_SORT_CONTEXT sortContext = context;
256  PVOID node1 = *(PVOID *)elem1;
257  PVOID node2 = *(PVOID *)elem2;
258  LONG result;
259 
260  result = sortContext->SortFunction(node1, node2, sortContext->SubId, sortContext->Context);
261 
262  return sortContext->PostSortFunction(result, node1, node2, sortContext->SortOrder);
263 }
264 
266  _In_ PPH_TREENEW_NODE *Nodes,
267  _In_ ULONG NumberOfNodes,
268  _In_ ULONG SortColumn,
269  _In_ PH_SORT_ORDER SortOrder,
270  _In_ PPH_CM_MANAGER Manager
271  )
272 {
273  PH_TREENEW_COLUMN tnColumn;
274  PPH_CM_COLUMN column;
275  PH_CM_SORT_CONTEXT sortContext;
276 
277  if (SortColumn < Manager->MinId)
278  return FALSE;
279 
280  if (!TreeNew_GetColumn(Manager->Handle, SortColumn, &tnColumn))
281  return TRUE;
282 
283  column = tnColumn.Context;
284 
285  if (!column->SortFunction)
286  return TRUE;
287 
288  sortContext.SortFunction = column->SortFunction;
289  sortContext.SubId = column->SubId;
290  sortContext.Context = column->Context;
291  sortContext.PostSortFunction = Manager->PostSortFunction;
292  sortContext.SortOrder = SortOrder;
293  qsort_s(Nodes, NumberOfNodes, sizeof(PVOID), PhCmpSortFunction, &sortContext);
294 
295  return TRUE;
296 }
297 
299  _In_ HWND TreeNewHandle,
300  _In_ PPH_STRINGREF Settings
301  )
302 {
303  return PhCmLoadSettingsEx(TreeNewHandle, NULL, 0, Settings, NULL);
304 }
305 
307  _In_ HWND TreeNewHandle,
308  _In_opt_ PPH_CM_MANAGER Manager,
309  _In_ ULONG Flags,
310  _In_ PPH_STRINGREF Settings,
311  _In_opt_ PPH_STRINGREF SortSettings
312  )
313 {
314  BOOLEAN result = FALSE;
315  PH_STRINGREF columnPart;
316  PH_STRINGREF remainingColumnPart;
317  PH_STRINGREF valuePart;
318  PH_STRINGREF subPart;
319  ULONG64 integer;
320  ULONG total;
321  BOOLEAN hasFixedColumn;
322  ULONG count;
323  ULONG i;
324  PPH_HASHTABLE columnHashtable;
325  PH_HASHTABLE_ENUM_CONTEXT enumContext;
326  PPH_KEY_VALUE_PAIR pair;
327  LONG orderArray[PH_CM_ORDER_LIMIT];
328  LONG maxOrder;
329 
330  if (Settings->Length != 0)
331  {
332  columnHashtable = PhCreateSimpleHashtable(20);
333 
334  remainingColumnPart = *Settings;
335 
336  while (remainingColumnPart.Length != 0)
337  {
338  PPH_TREENEW_COLUMN column;
339  ULONG id;
340  ULONG displayIndex;
341  ULONG width;
342 
343  PhSplitStringRefAtChar(&remainingColumnPart, '|', &columnPart, &remainingColumnPart);
344 
345  if (columnPart.Length != 0)
346  {
347  // Id
348 
349  PhSplitStringRefAtChar(&columnPart, ',', &valuePart, &columnPart);
350 
351  if (valuePart.Length == 0)
352  goto CleanupExit;
353 
354  if (valuePart.Buffer[0] == '+')
355  {
356  PH_STRINGREF pluginName;
357  ULONG subId;
358  PPH_CM_COLUMN cmColumn;
359 
360  // This is a plugin-owned column.
361 
362  if (!Manager)
363  continue;
364  if (!PhEmParseCompoundId(&valuePart, &pluginName, &subId))
365  continue;
366 
367  cmColumn = PhCmFindColumn(Manager, &pluginName, subId);
368 
369  if (!cmColumn)
370  continue; // can't find the column, skip this part
371 
372  id = cmColumn->Id;
373  }
374  else
375  {
376  if (!PhStringToInteger64(&valuePart, 10, &integer))
377  goto CleanupExit;
378 
379  id = (ULONG)integer;
380  }
381 
382  // Display Index
383 
384  PhSplitStringRefAtChar(&columnPart, ',', &valuePart, &columnPart);
385 
386  if (!(Flags & PH_CM_COLUMN_WIDTHS_ONLY))
387  {
388  if (valuePart.Length == 0 || !PhStringToInteger64(&valuePart, 10, &integer))
389  goto CleanupExit;
390 
391  displayIndex = (ULONG)integer;
392  }
393  else
394  {
395  if (valuePart.Length != 0)
396  goto CleanupExit;
397 
398  displayIndex = -1;
399  }
400 
401  // Width
402 
403  if (columnPart.Length == 0 || !PhStringToInteger64(&columnPart, 10, &integer))
404  goto CleanupExit;
405 
406  width = (ULONG)integer;
407 
408  column = PhAllocate(sizeof(PH_TREENEW_COLUMN));
409  column->Id = id;
410  column->DisplayIndex = displayIndex;
411  column->Width = width;
412  PhAddItemSimpleHashtable(columnHashtable, (PVOID)column->Id, column);
413  }
414  }
415 
416  TreeNew_SetRedraw(TreeNewHandle, FALSE);
417 
418  // Set visibility and width.
419 
420  i = 0;
421  count = 0;
422  total = TreeNew_GetColumnCount(TreeNewHandle);
423  hasFixedColumn = !!TreeNew_GetFixedColumn(TreeNewHandle);
424  memset(orderArray, 0, sizeof(orderArray));
425  maxOrder = 0;
426 
427  while (count < total)
428  {
429  PH_TREENEW_COLUMN setColumn;
430  PPH_TREENEW_COLUMN *columnPtr;
431 
432  if (TreeNew_GetColumn(TreeNewHandle, i, &setColumn))
433  {
434  columnPtr = (PPH_TREENEW_COLUMN *)PhFindItemSimpleHashtable(columnHashtable, (PVOID)i);
435 
436  if (!(Flags & PH_CM_COLUMN_WIDTHS_ONLY))
437  {
438  if (columnPtr)
439  {
440  setColumn.Visible = TRUE;
441  setColumn.Width = (*columnPtr)->Width;
442  TreeNew_SetColumn(TreeNewHandle, TN_COLUMN_FLAG_VISIBLE | TN_COLUMN_WIDTH, &setColumn);
443 
444  if (!setColumn.Fixed)
445  {
446  // For compatibility reasons, normal columns have their display indicies stored
447  // one higher than usual (so they start from 1, not 0). Fix that here.
448  if (hasFixedColumn && (*columnPtr)->DisplayIndex != 0)
449  (*columnPtr)->DisplayIndex--;
450 
451  if ((*columnPtr)->DisplayIndex < PH_CM_ORDER_LIMIT)
452  {
453  orderArray[(*columnPtr)->DisplayIndex] = i;
454 
455  if ((ULONG)maxOrder < (*columnPtr)->DisplayIndex + 1)
456  maxOrder = (*columnPtr)->DisplayIndex + 1;
457  }
458  }
459  }
460  else if (!setColumn.Fixed) // never hide the fixed column
461  {
462  setColumn.Visible = FALSE;
463  TreeNew_SetColumn(TreeNewHandle, TN_COLUMN_FLAG_VISIBLE, &setColumn);
464  }
465  }
466  else
467  {
468  if (columnPtr)
469  {
470  setColumn.Width = (*columnPtr)->Width;
471  TreeNew_SetColumn(TreeNewHandle, TN_COLUMN_WIDTH, &setColumn);
472  }
473  }
474 
475  count++;
476  }
477 
478  i++;
479  }
480 
481  if (!(Flags & PH_CM_COLUMN_WIDTHS_ONLY))
482  {
483  // Set the order array.
484  TreeNew_SetColumnOrderArray(TreeNewHandle, maxOrder, orderArray);
485  }
486 
487  TreeNew_SetRedraw(TreeNewHandle, TRUE);
488 
489  result = TRUE;
490 
491 CleanupExit:
492  PhBeginEnumHashtable(columnHashtable, &enumContext);
493 
494  while (pair = PhNextEnumHashtable(&enumContext))
495  PhFree(pair->Value);
496 
497  PhDereferenceObject(columnHashtable);
498  }
499 
500  // Load sort settings.
501 
502  if (SortSettings && SortSettings->Length != 0)
503  {
504  PhSplitStringRefAtChar(SortSettings, ',', &valuePart, &subPart);
505 
506  if (valuePart.Length != 0 && subPart.Length != 0)
507  {
508  ULONG sortColumn;
509  PH_SORT_ORDER sortOrder;
510 
511  sortColumn = -1;
512 
513  if (valuePart.Buffer[0] == '+')
514  {
515  PH_STRINGREF pluginName;
516  ULONG subId;
517  PPH_CM_COLUMN cmColumn;
518 
519  if (
520  Manager &&
521  PhEmParseCompoundId(&valuePart, &pluginName, &subId) &&
522  (cmColumn = PhCmFindColumn(Manager, &pluginName, subId))
523  )
524  {
525  sortColumn = cmColumn->Id;
526  }
527  }
528  else
529  {
530  PhStringToInteger64(&valuePart, 10, &integer);
531  sortColumn = (ULONG)integer;
532  }
533 
534  PhStringToInteger64(&subPart, 10, &integer);
535  sortOrder = (PH_SORT_ORDER)integer;
536 
537  if (sortColumn != -1 && sortOrder <= DescendingSortOrder)
538  {
539  TreeNew_SetSort(TreeNewHandle, sortColumn, sortOrder);
540  }
541  }
542  }
543 
544  return result;
545 }
546 
548  _In_ HWND TreeNewHandle
549  )
550 {
551  return PhCmSaveSettingsEx(TreeNewHandle, NULL, 0, NULL);
552 }
553 
555  _In_ HWND TreeNewHandle,
556  _In_opt_ PPH_CM_MANAGER Manager,
557  _In_ ULONG Flags,
558  _Out_opt_ PPH_STRING *SortSettings
559  )
560 {
561  PH_STRING_BUILDER stringBuilder;
562  ULONG i = 0;
563  ULONG count = 0;
564  ULONG total;
565  ULONG increment;
566  PH_TREENEW_COLUMN column;
567 
568  total = TreeNew_GetColumnCount(TreeNewHandle);
569 
570  if (TreeNew_GetFixedColumn(TreeNewHandle))
571  increment = 1; // the first normal column should have a display index that starts with 1, for compatibility
572  else
573  increment = 0;
574 
575  PhInitializeStringBuilder(&stringBuilder, 100);
576 
577  while (count < total)
578  {
579  if (TreeNew_GetColumn(TreeNewHandle, i, &column))
580  {
581  if (!(Flags & PH_CM_COLUMN_WIDTHS_ONLY))
582  {
583  if (column.Visible)
584  {
585  if (!Manager || i < Manager->MinId)
586  {
588  &stringBuilder,
589  L"%u,%u,%u|",
590  i,
591  column.Fixed ? 0 : column.DisplayIndex + increment,
592  column.Width
593  );
594  }
595  else
596  {
597  PPH_CM_COLUMN cmColumn;
598 
599  cmColumn = column.Context;
601  &stringBuilder,
602  L"+%s+%u,%u,%u|",
603  cmColumn->Plugin->Name.Buffer,
604  cmColumn->SubId,
605  column.DisplayIndex + increment,
606  column.Width
607  );
608  }
609  }
610  }
611  else
612  {
613  if (!Manager || i < Manager->MinId)
614  {
616  &stringBuilder,
617  L"%u,,%u|",
618  i,
619  column.Width
620  );
621  }
622  else
623  {
624  PPH_CM_COLUMN cmColumn;
625 
626  cmColumn = column.Context;
628  &stringBuilder,
629  L"+%s+%u,,%u|",
630  cmColumn->Plugin->Name.Buffer,
631  cmColumn->SubId,
632  column.Width
633  );
634  }
635  }
636 
637  count++;
638  }
639 
640  i++;
641  }
642 
643  if (stringBuilder.String->Length != 0)
644  PhRemoveEndStringBuilder(&stringBuilder, 1);
645 
646  if (SortSettings)
647  {
648  ULONG sortColumn;
649  PH_SORT_ORDER sortOrder;
650 
651  if (TreeNew_GetSort(TreeNewHandle, &sortColumn, &sortOrder))
652  {
653  if (sortOrder != NoSortOrder)
654  {
655  if (!Manager || sortColumn < Manager->MinId)
656  {
657  *SortSettings = PhFormatString(L"%u,%u", sortColumn, sortOrder);
658  }
659  else
660  {
661  PH_TREENEW_COLUMN column;
662  PPH_CM_COLUMN cmColumn;
663 
664  if (TreeNew_GetColumn(TreeNewHandle, sortColumn, &column))
665  {
666  cmColumn = column.Context;
667  *SortSettings = PhFormatString(L"+%s+%u,%u", cmColumn->Plugin->Name.Buffer, cmColumn->SubId, sortOrder);
668  }
669  else
670  {
671  *SortSettings = PhReferenceEmptyString();
672  }
673  }
674  }
675  else
676  {
677  *SortSettings = PhCreateString(L"0,0");
678  }
679  }
680  else
681  {
682  *SortSettings = PhReferenceEmptyString();
683  }
684  }
685 
686  return PhFinalStringBuilderString(&stringBuilder);
687 }