xref: /reactos/base/setup/usetup/partlist.c (revision 3476cdae)
1 /*
2  *  ReactOS kernel
3  *  Copyright (C) 2002, 2003, 2004, 2005 ReactOS Team
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License along
16  *  with this program; if not, write to the Free Software Foundation, Inc.,
17  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 /*
20  * COPYRIGHT:       See COPYING in the top level directory
21  * PROJECT:         ReactOS text-mode setup
22  * FILE:            base/setup/usetup/partlist.c
23  * PURPOSE:         Partition list functions
24  * PROGRAMMER:      Casper S. Hornstrup (chorns@users.sourceforge.net)
25  */
26 
27 #include "usetup.h"
28 
29 #define NDEBUG
30 #include <debug.h>
31 
32 /* HELPERS FOR DISK AND PARTITION DESCRIPTIONS ******************************/
33 
34 VOID
35 GetPartitionTypeString(
36     IN PPARTENTRY PartEntry,
37     OUT PSTR strBuffer,
38     IN ULONG cchBuffer)
39 {
40     if (PartEntry->PartitionType == PARTITION_ENTRY_UNUSED)
41     {
42         RtlStringCchCopyA(strBuffer, cchBuffer,
43                           MUIGetString(STRING_FORMATUNUSED));
44     }
45     else if (IsContainerPartition(PartEntry->PartitionType))
46     {
47         RtlStringCchCopyA(strBuffer, cchBuffer,
48                           MUIGetString(STRING_EXTENDED_PARTITION));
49     }
50     else
51     {
52         UINT i;
53 
54         /* Do the table lookup */
55         if (PartEntry->DiskEntry->DiskStyle == PARTITION_STYLE_MBR)
56         {
57             for (i = 0; i < ARRAYSIZE(MbrPartitionTypes); ++i)
58             {
59                 if (PartEntry->PartitionType == MbrPartitionTypes[i].Type)
60                 {
61                     RtlStringCchCopyA(strBuffer, cchBuffer,
62                                       MbrPartitionTypes[i].Description);
63                     return;
64                 }
65             }
66         }
67 #if 0 // TODO: GPT support!
68         else if (PartEntry->DiskEntry->DiskStyle == PARTITION_STYLE_GPT)
69         {
70             for (i = 0; i < ARRAYSIZE(GptPartitionTypes); ++i)
71             {
72                 if (IsEqualPartitionType(PartEntry->PartitionType,
73                                          GptPartitionTypes[i].Guid))
74                 {
75                     RtlStringCchCopyA(strBuffer, cchBuffer,
76                                       GptPartitionTypes[i].Description);
77                     return;
78                 }
79             }
80         }
81 #endif
82 
83         /* We are here because the partition type is unknown */
84         if (cchBuffer > 0) *strBuffer = '\0';
85     }
86 
87     if ((cchBuffer > 0) && (*strBuffer == '\0'))
88     {
89         RtlStringCchPrintfA(strBuffer, cchBuffer,
90                             MUIGetString(STRING_PARTTYPE),
91                             PartEntry->PartitionType);
92     }
93 }
94 
95 VOID
96 PrettifySize1(
97     IN OUT PULONGLONG Size,
98     OUT PCSTR* Unit)
99 {
100     ULONGLONG DiskSize = *Size;
101 
102     if (DiskSize >= 10 * GB) /* 10 GB */
103     {
104         DiskSize = RoundingDivide(DiskSize, GB);
105         *Unit = MUIGetString(STRING_GB);
106     }
107     else
108     {
109         DiskSize = RoundingDivide(DiskSize, MB);
110         if (DiskSize == 0)
111             DiskSize = 1;
112         *Unit = MUIGetString(STRING_MB);
113     }
114 
115     *Size = DiskSize;
116 }
117 
118 VOID
119 PrettifySize2(
120     IN OUT PULONGLONG Size,
121     OUT PCSTR* Unit)
122 {
123     ULONGLONG PartSize = *Size;
124 
125 #if 0
126     if (PartSize >= 10 * GB) /* 10 GB */
127     {
128         PartSize = RoundingDivide(PartSize, GB);
129         *Unit = MUIGetString(STRING_GB);
130     }
131     else
132 #endif
133     if (PartSize >= 10 * MB) /* 10 MB */
134     {
135         PartSize = RoundingDivide(PartSize, MB);
136         *Unit = MUIGetString(STRING_MB);
137     }
138     else
139     {
140         PartSize = RoundingDivide(PartSize, KB);
141         *Unit = MUIGetString(STRING_KB);
142     }
143 
144     *Size = PartSize;
145 }
146 
147 VOID
148 PartitionDescription(
149     IN PPARTENTRY PartEntry,
150     OUT PSTR strBuffer,
151     IN SIZE_T cchBuffer)
152 {
153     PSTR pBuffer = strBuffer;
154     size_t cchBufferSize = cchBuffer;
155     ULONGLONG PartSize;
156     PCSTR Unit;
157 
158     /* Get the partition size */
159     PartSize = PartEntry->SectorCount.QuadPart * PartEntry->DiskEntry->BytesPerSector;
160     PrettifySize2(&PartSize, &Unit);
161 
162     if (PartEntry->IsPartitioned == FALSE)
163     {
164         /* Unpartitioned space: Just display the description and size */
165         RtlStringCchPrintfExA(pBuffer, cchBufferSize,
166                               &pBuffer, &cchBufferSize, 0,
167                               "     %s%-.30s",
168                               PartEntry->LogicalPartition ? "  " : "", // Optional indentation
169                               MUIGetString(STRING_UNPSPACE));
170 
171         RtlStringCchPrintfA(pBuffer, cchBufferSize,
172                             "%*s%6I64u %s",
173                             38 - min(strlen(strBuffer), 38), "", // Indentation
174                             PartSize,
175                             Unit);
176         return;
177     }
178 
179 //
180 // NOTE: This could be done with the next case.
181 //
182     if ((PartEntry->DiskEntry->DiskStyle == PARTITION_STYLE_MBR) &&
183         IsContainerPartition(PartEntry->PartitionType))
184     {
185         /* Extended partition container: Just display the partition's type and size */
186         RtlStringCchPrintfExA(pBuffer, cchBufferSize,
187                               &pBuffer, &cchBufferSize, 0,
188                               "     %-.30s",
189                               MUIGetString(STRING_EXTENDED_PARTITION));
190 
191         RtlStringCchPrintfA(pBuffer, cchBufferSize,
192                             "%*s%6I64u %s",
193                             38 - min(strlen(strBuffer), 38), "", // Indentation
194                             PartSize,
195                             Unit);
196         return;
197     }
198 
199     /*
200      * Not an extended partition container.
201      */
202 
203     /* Drive letter and partition number */
204     RtlStringCchPrintfExA(pBuffer, cchBufferSize,
205                           &pBuffer, &cchBufferSize, 0,
206                           "%c%c %c %s(%lu) ",
207                           (PartEntry->DriveLetter == 0) ? '-' : (CHAR)PartEntry->DriveLetter,
208                           (PartEntry->DriveLetter == 0) ? '-' : ':',
209                           PartEntry->BootIndicator ? '*' : ' ',
210                           PartEntry->LogicalPartition ? "  " : "", // Optional indentation
211                           PartEntry->PartitionNumber);
212 
213     /*
214      * If the volume's file system is recognized, display the volume label
215      * (if any) and the file system name. Otherwise, display the partition
216      * type if it's not a new partition.
217      */
218     if (!PartEntry->New && *PartEntry->FileSystem &&
219         _wcsicmp(PartEntry->FileSystem, L"RAW") != 0)
220     {
221         size_t cchLabelSize = 0;
222         if (*PartEntry->VolumeLabel)
223         {
224             RtlStringCchPrintfExA(pBuffer, cchBufferSize,
225                                   &pBuffer, &cchLabelSize, 0,
226                                   "\"%-.11S\" ",
227                                   PartEntry->VolumeLabel);
228             cchLabelSize = cchBufferSize - cchLabelSize; // Actual length of the label part.
229             cchBufferSize -= cchLabelSize; // And reset cchBufferSize to what it should be.
230         }
231 
232         // TODO: Group this part together with the similar one
233         // from below once the strings are in the same encoding...
234         RtlStringCchPrintfExA(pBuffer, cchBufferSize,
235                               &pBuffer, &cchBufferSize, 0,
236                               "[%-.*S]",
237                               /* The minimum length can be at most 11 since
238                                * cchLabelSize can be at most == 11 + 3 == 14 */
239                               25 - min(cchLabelSize, 25),
240                               PartEntry->FileSystem);
241     }
242     else
243     {
244         CHAR PartTypeString[32];
245         PCSTR PartType = PartTypeString;
246 
247         if (PartEntry->New)
248         {
249             /* Use this description if the partition is new (and thus, not formatted) */
250             PartType = MUIGetString(STRING_UNFORMATTED);
251         }
252         else
253         {
254             /* If the partition is not new but its file system is not recognized
255              * (or is not formatted), use the partition type description. */
256             GetPartitionTypeString(PartEntry,
257                                    PartTypeString,
258                                    ARRAYSIZE(PartTypeString));
259             PartType = PartTypeString;
260         }
261         if (!PartType || !*PartType)
262         {
263             PartType = MUIGetString(STRING_FORMATUNKNOWN);
264         }
265 
266         // TODO: Group this part together with the similar one
267         // from above once the strings are in the same encoding...
268         RtlStringCchPrintfExA(pBuffer, cchBufferSize,
269                               &pBuffer, &cchBufferSize, 0,
270                               "[%-.*s]",
271                               25,
272                               PartType);
273     }
274 
275     /* Show the remaining free space only if a FS is mounted */
276     // FIXME: We don't support that yet!
277 #if 0
278     if (*PartEntry->FileSystem)
279     {
280         RtlStringCchPrintfA(pBuffer, cchBufferSize,
281                             "%*s%6I64u %s (%6I64u %s %s)",
282                             38 - min(strlen(strBuffer), 38), "", // Indentation
283                             PartSize,
284                             Unit,
285                             PartFreeSize,
286                             Unit,
287                             "free");
288     }
289     else
290 #endif
291     {
292         RtlStringCchPrintfA(pBuffer, cchBufferSize,
293                             "%*s%6I64u %s",
294                             38 - min(strlen(strBuffer), 38), "", // Indentation
295                             PartSize,
296                             Unit);
297     }
298 }
299 
300 VOID
301 DiskDescription(
302     IN PDISKENTRY DiskEntry,
303     OUT PSTR strBuffer,
304     IN SIZE_T cchBuffer)
305 {
306     ULONGLONG DiskSize;
307     PCSTR Unit;
308 
309     /* Get the disk size */
310     DiskSize = DiskEntry->SectorCount.QuadPart * DiskEntry->BytesPerSector;
311     PrettifySize1(&DiskSize, &Unit);
312 
313     //
314     // FIXME: We *MUST* use TXTSETUP.SIF strings from section "DiskDriverMap" !!
315     //
316     if (DiskEntry->DriverName.Length > 0)
317     {
318         RtlStringCchPrintfA(strBuffer, cchBuffer,
319                             MUIGetString(STRING_HDDINFO_1),
320                             DiskSize,
321                             Unit,
322                             DiskEntry->DiskNumber,
323                             DiskEntry->Port,
324                             DiskEntry->Bus,
325                             DiskEntry->Id,
326                             &DiskEntry->DriverName,
327                             DiskEntry->DiskStyle == PARTITION_STYLE_MBR ? "MBR" :
328                             DiskEntry->DiskStyle == PARTITION_STYLE_GPT ? "GPT" :
329                                                                           "RAW");
330     }
331     else
332     {
333         RtlStringCchPrintfA(strBuffer, cchBuffer,
334                             MUIGetString(STRING_HDDINFO_2),
335                             DiskSize,
336                             Unit,
337                             DiskEntry->DiskNumber,
338                             DiskEntry->Port,
339                             DiskEntry->Bus,
340                             DiskEntry->Id,
341                             DiskEntry->DiskStyle == PARTITION_STYLE_MBR ? "MBR" :
342                             DiskEntry->DiskStyle == PARTITION_STYLE_GPT ? "GPT" :
343                                                                           "RAW");
344     }
345 }
346 
347 
348 /* FUNCTIONS ****************************************************************/
349 
350 VOID
351 InitPartitionListUi(
352     IN OUT PPARTLIST_UI ListUi,
353     IN PPARTLIST List,
354     IN PPARTENTRY CurrentEntry OPTIONAL,
355     IN SHORT Left,
356     IN SHORT Top,
357     IN SHORT Right,
358     IN SHORT Bottom)
359 {
360     ListUi->List = List;
361     // ListUi->FirstShown = NULL;
362     // ListUi->LastShown = NULL;
363 
364     ListUi->Left = Left;
365     ListUi->Top = Top;
366     ListUi->Right = Right;
367     ListUi->Bottom = Bottom;
368 
369     ListUi->Line = 0;
370     ListUi->Offset = 0;
371 
372     // ListUi->Redraw = TRUE;
373 
374     /* Search for first usable disk and partition */
375     if (!CurrentEntry)
376     {
377         ListUi->CurrentDisk = NULL;
378         ListUi->CurrentPartition = NULL;
379 
380         if (!IsListEmpty(&List->DiskListHead))
381         {
382             ListUi->CurrentDisk = CONTAINING_RECORD(List->DiskListHead.Flink,
383                                                     DISKENTRY, ListEntry);
384 
385             if (!IsListEmpty(&ListUi->CurrentDisk->PrimaryPartListHead))
386             {
387                 ListUi->CurrentPartition = CONTAINING_RECORD(ListUi->CurrentDisk->PrimaryPartListHead.Flink,
388                                                              PARTENTRY, ListEntry);
389             }
390         }
391     }
392     else
393     {
394         /*
395          * The CurrentEntry must belong to the associated partition list,
396          * and the latter must therefore not be empty.
397          */
398         ASSERT(!IsListEmpty(&List->DiskListHead));
399         ASSERT(CurrentEntry->DiskEntry->PartList == List);
400 
401         ListUi->CurrentPartition = CurrentEntry;
402         ListUi->CurrentDisk = CurrentEntry->DiskEntry;
403     }
404 }
405 
406 static
407 VOID
408 PrintEmptyLine(
409     IN PPARTLIST_UI ListUi)
410 {
411     COORD coPos;
412     ULONG Written;
413     USHORT Width;
414     USHORT Height;
415 
416     Width = ListUi->Right - ListUi->Left - 1;
417     Height = ListUi->Bottom - ListUi->Top - 2;
418 
419     coPos.X = ListUi->Left + 1;
420     coPos.Y = ListUi->Top + 1 + ListUi->Line;
421 
422     if (ListUi->Line >= 0 && ListUi->Line <= Height)
423     {
424         FillConsoleOutputAttribute(StdOutput,
425                                    FOREGROUND_WHITE | BACKGROUND_BLUE,
426                                    Width,
427                                    coPos,
428                                    &Written);
429 
430         FillConsoleOutputCharacterA(StdOutput,
431                                     ' ',
432                                     Width,
433                                     coPos,
434                                     &Written);
435     }
436 
437     ListUi->Line++;
438 }
439 
440 static
441 VOID
442 PrintPartitionData(
443     IN PPARTLIST_UI ListUi,
444     IN PDISKENTRY DiskEntry,
445     IN PPARTENTRY PartEntry)
446 {
447     COORD coPos;
448     ULONG Written;
449     USHORT Width;
450     USHORT Height;
451     UCHAR Attribute;
452     CHAR LineBuffer[100];
453 
454     PartitionDescription(PartEntry, LineBuffer, ARRAYSIZE(LineBuffer));
455 
456     Width = ListUi->Right - ListUi->Left - 1;
457     Height = ListUi->Bottom - ListUi->Top - 2;
458 
459     coPos.X = ListUi->Left + 1;
460     coPos.Y = ListUi->Top + 1 + ListUi->Line;
461 
462     Attribute = (ListUi->CurrentDisk == DiskEntry &&
463                  ListUi->CurrentPartition == PartEntry) ?
464                  FOREGROUND_BLUE | BACKGROUND_WHITE :
465                  FOREGROUND_WHITE | BACKGROUND_BLUE;
466 
467     if (ListUi->Line >= 0 && ListUi->Line <= Height)
468     {
469         FillConsoleOutputCharacterA(StdOutput,
470                                     ' ',
471                                     Width,
472                                     coPos,
473                                     &Written);
474     }
475     coPos.X += 4;
476     Width -= 8;
477     if (ListUi->Line >= 0 && ListUi->Line <= Height)
478     {
479         FillConsoleOutputAttribute(StdOutput,
480                                    Attribute,
481                                    Width,
482                                    coPos,
483                                    &Written);
484     }
485     coPos.X++;
486     Width -= 2;
487     if (ListUi->Line >= 0 && ListUi->Line <= Height)
488     {
489         WriteConsoleOutputCharacterA(StdOutput,
490                                      LineBuffer,
491                                      min(strlen(LineBuffer), Width),
492                                      coPos,
493                                      &Written);
494     }
495 
496     ListUi->Line++;
497 }
498 
499 static
500 VOID
501 PrintDiskData(
502     IN PPARTLIST_UI ListUi,
503     IN PDISKENTRY DiskEntry)
504 {
505     PPARTENTRY PrimaryPartEntry, LogicalPartEntry;
506     PLIST_ENTRY PrimaryEntry, LogicalEntry;
507     COORD coPos;
508     ULONG Written;
509     USHORT Width;
510     USHORT Height;
511     CHAR LineBuffer[100];
512 
513     DiskDescription(DiskEntry, LineBuffer, ARRAYSIZE(LineBuffer));
514 
515     Width = ListUi->Right - ListUi->Left - 1;
516     Height = ListUi->Bottom - ListUi->Top - 2;
517 
518     coPos.X = ListUi->Left + 1;
519     coPos.Y = ListUi->Top + 1 + ListUi->Line;
520 
521     if (ListUi->Line >= 0 && ListUi->Line <= Height)
522     {
523         FillConsoleOutputAttribute(StdOutput,
524                                    FOREGROUND_WHITE | BACKGROUND_BLUE,
525                                    Width,
526                                    coPos,
527                                    &Written);
528 
529         FillConsoleOutputCharacterA(StdOutput,
530                                     ' ',
531                                     Width,
532                                     coPos,
533                                     &Written);
534     }
535 
536     coPos.X++;
537     if (ListUi->Line >= 0 && ListUi->Line <= Height)
538     {
539         WriteConsoleOutputCharacterA(StdOutput,
540                                      LineBuffer,
541                                      min((USHORT)strlen(LineBuffer), Width - 2),
542                                      coPos,
543                                      &Written);
544     }
545 
546     ListUi->Line++;
547 
548     /* Print separator line */
549     PrintEmptyLine(ListUi);
550 
551     /* Print partition lines */
552     for (PrimaryEntry = DiskEntry->PrimaryPartListHead.Flink;
553          PrimaryEntry != &DiskEntry->PrimaryPartListHead;
554          PrimaryEntry = PrimaryEntry->Flink)
555     {
556         PrimaryPartEntry = CONTAINING_RECORD(PrimaryEntry, PARTENTRY, ListEntry);
557 
558         PrintPartitionData(ListUi,
559                            DiskEntry,
560                            PrimaryPartEntry);
561 
562         if (IsContainerPartition(PrimaryPartEntry->PartitionType))
563         {
564             for (LogicalEntry = DiskEntry->LogicalPartListHead.Flink;
565                  LogicalEntry != &DiskEntry->LogicalPartListHead;
566                  LogicalEntry = LogicalEntry->Flink)
567             {
568                 LogicalPartEntry = CONTAINING_RECORD(LogicalEntry, PARTENTRY, ListEntry);
569 
570                 PrintPartitionData(ListUi,
571                                    DiskEntry,
572                                    LogicalPartEntry);
573             }
574         }
575     }
576 
577     /* Print separator line */
578     PrintEmptyLine(ListUi);
579 }
580 
581 VOID
582 DrawPartitionList(
583     IN PPARTLIST_UI ListUi)
584 {
585     PPARTLIST List = ListUi->List;
586     PLIST_ENTRY Entry, Entry2;
587     PDISKENTRY DiskEntry;
588     PPARTENTRY PartEntry = NULL;
589     COORD coPos;
590     ULONG Written;
591     USHORT Width;
592     USHORT Height;
593     SHORT i;
594     SHORT CurrentDiskLine;
595     SHORT CurrentPartLine;
596     SHORT LastLine;
597     BOOLEAN CurrentPartLineFound = FALSE;
598     BOOLEAN CurrentDiskLineFound = FALSE;
599 
600     Width = ListUi->Right - ListUi->Left - 1;
601     Height = ListUi->Bottom - ListUi->Top - 2;
602 
603     /* Calculate the line of the current disk and partition */
604     CurrentDiskLine = 0;
605     CurrentPartLine = 0;
606     LastLine = 0;
607 
608     for (Entry = List->DiskListHead.Flink;
609          Entry != &List->DiskListHead;
610          Entry = Entry->Flink)
611     {
612         DiskEntry = CONTAINING_RECORD(Entry, DISKENTRY, ListEntry);
613 
614         LastLine += 2;
615         if (CurrentPartLineFound == FALSE)
616         {
617             CurrentPartLine += 2;
618         }
619 
620         for (Entry2 = DiskEntry->PrimaryPartListHead.Flink;
621              Entry2 != &DiskEntry->PrimaryPartListHead;
622              Entry2 = Entry2->Flink)
623         {
624             PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
625             if (PartEntry == ListUi->CurrentPartition)
626             {
627                 CurrentPartLineFound = TRUE;
628             }
629 
630             if (CurrentPartLineFound == FALSE)
631             {
632                 CurrentPartLine++;
633             }
634 
635             LastLine++;
636         }
637 
638         if (CurrentPartLineFound == FALSE)
639         {
640             for (Entry2 = DiskEntry->LogicalPartListHead.Flink;
641                  Entry2 != &DiskEntry->LogicalPartListHead;
642                  Entry2 = Entry2->Flink)
643             {
644                 PartEntry = CONTAINING_RECORD(Entry2, PARTENTRY, ListEntry);
645                 if (PartEntry == ListUi->CurrentPartition)
646                 {
647                     CurrentPartLineFound = TRUE;
648                 }
649 
650                 if (CurrentPartLineFound == FALSE)
651                 {
652                     CurrentPartLine++;
653                 }
654 
655                 LastLine++;
656             }
657         }
658 
659         if (DiskEntry == ListUi->CurrentDisk)
660         {
661             CurrentDiskLineFound = TRUE;
662         }
663 
664         if (Entry->Flink != &List->DiskListHead)
665         {
666             if (CurrentDiskLineFound == FALSE)
667             {
668                 CurrentPartLine++;
669                 CurrentDiskLine = CurrentPartLine;
670             }
671 
672             LastLine++;
673         }
674         else
675         {
676             LastLine--;
677         }
678     }
679 
680     /* If it possible, make the disk name visible */
681     if (CurrentPartLine < ListUi->Offset)
682     {
683         ListUi->Offset = CurrentPartLine;
684     }
685     else if (CurrentPartLine - ListUi->Offset > Height)
686     {
687         ListUi->Offset = CurrentPartLine - Height;
688     }
689 
690     if (CurrentDiskLine < ListUi->Offset && CurrentPartLine - CurrentDiskLine < Height)
691     {
692         ListUi->Offset = CurrentDiskLine;
693     }
694 
695     /* Draw upper left corner */
696     coPos.X = ListUi->Left;
697     coPos.Y = ListUi->Top;
698     FillConsoleOutputCharacterA(StdOutput,
699                                 0xDA, // '+',
700                                 1,
701                                 coPos,
702                                 &Written);
703 
704     /* Draw upper edge */
705     coPos.X = ListUi->Left + 1;
706     coPos.Y = ListUi->Top;
707     if (ListUi->Offset == 0)
708     {
709         FillConsoleOutputCharacterA(StdOutput,
710                                     0xC4, // '-',
711                                     Width,
712                                     coPos,
713                                     &Written);
714     }
715     else
716     {
717         FillConsoleOutputCharacterA(StdOutput,
718                                     0xC4, // '-',
719                                     Width - 4,
720                                     coPos,
721                                     &Written);
722         coPos.X = ListUi->Right - 5;
723         WriteConsoleOutputCharacterA(StdOutput,
724                                      "(\x18)", // "(up)"
725                                      3,
726                                      coPos,
727                                      &Written);
728         coPos.X = ListUi->Right - 2;
729         FillConsoleOutputCharacterA(StdOutput,
730                                     0xC4, // '-',
731                                     2,
732                                     coPos,
733                                     &Written);
734     }
735 
736     /* Draw upper right corner */
737     coPos.X = ListUi->Right;
738     coPos.Y = ListUi->Top;
739     FillConsoleOutputCharacterA(StdOutput,
740                                 0xBF, // '+',
741                                 1,
742                                 coPos,
743                                 &Written);
744 
745     /* Draw left and right edge */
746     for (i = ListUi->Top + 1; i < ListUi->Bottom; i++)
747     {
748         coPos.X = ListUi->Left;
749         coPos.Y = i;
750         FillConsoleOutputCharacterA(StdOutput,
751                                     0xB3, // '|',
752                                     1,
753                                     coPos,
754                                     &Written);
755 
756         coPos.X = ListUi->Right;
757         FillConsoleOutputCharacterA(StdOutput,
758                                     0xB3, //'|',
759                                     1,
760                                     coPos,
761                                     &Written);
762     }
763 
764     /* Draw lower left corner */
765     coPos.X = ListUi->Left;
766     coPos.Y = ListUi->Bottom;
767     FillConsoleOutputCharacterA(StdOutput,
768                                 0xC0, // '+',
769                                 1,
770                                 coPos,
771                                 &Written);
772 
773     /* Draw lower edge */
774     coPos.X = ListUi->Left + 1;
775     coPos.Y = ListUi->Bottom;
776     if (LastLine - ListUi->Offset <= Height)
777     {
778         FillConsoleOutputCharacterA(StdOutput,
779                                     0xC4, // '-',
780                                     Width,
781                                     coPos,
782                                     &Written);
783     }
784     else
785     {
786         FillConsoleOutputCharacterA(StdOutput,
787                                     0xC4, // '-',
788                                     Width - 4,
789                                     coPos,
790                                     &Written);
791         coPos.X = ListUi->Right - 5;
792         WriteConsoleOutputCharacterA(StdOutput,
793                                      "(\x19)", // "(down)"
794                                      3,
795                                      coPos,
796                                      &Written);
797        coPos.X = ListUi->Right - 2;
798        FillConsoleOutputCharacterA(StdOutput,
799                                    0xC4, // '-',
800                                    2,
801                                    coPos,
802                                    &Written);
803     }
804 
805     /* Draw lower right corner */
806     coPos.X = ListUi->Right;
807     coPos.Y = ListUi->Bottom;
808     FillConsoleOutputCharacterA(StdOutput,
809                                 0xD9, // '+',
810                                 1,
811                                 coPos,
812                                 &Written);
813 
814     /* Print list entries */
815     ListUi->Line = -ListUi->Offset;
816 
817     for (Entry = List->DiskListHead.Flink;
818          Entry != &List->DiskListHead;
819          Entry = Entry->Flink)
820     {
821         DiskEntry = CONTAINING_RECORD(Entry, DISKENTRY, ListEntry);
822 
823         /* Print disk entry */
824         PrintDiskData(ListUi, DiskEntry);
825     }
826 }
827 
828 VOID
829 ScrollDownPartitionList(
830     IN PPARTLIST_UI ListUi)
831 {
832     PPARTENTRY NextPart = GetNextPartition(ListUi->List, ListUi->CurrentPartition);
833     if (NextPart)
834     {
835         ListUi->CurrentPartition = NextPart;
836         ListUi->CurrentDisk = NextPart->DiskEntry;
837         DrawPartitionList(ListUi);
838     }
839 }
840 
841 VOID
842 ScrollUpPartitionList(
843     IN PPARTLIST_UI ListUi)
844 {
845     PPARTENTRY PrevPart = GetPrevPartition(ListUi->List, ListUi->CurrentPartition);
846     if (PrevPart)
847     {
848         ListUi->CurrentPartition = PrevPart;
849         ListUi->CurrentDisk = PrevPart->DiskEntry;
850         DrawPartitionList(ListUi);
851     }
852 }
853 
854 /* EOF */
855