Index: llvm/test/tools/llvm-rc/Inputs/tag-menu-bad-menuitem-id.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-menu-bad-menuitem-id.rc @@ -0,0 +1,3 @@ +1 MENU { + MENUITEM "Wrong", 100000, CHECKED +} Index: llvm/test/tools/llvm-rc/Inputs/tag-menu.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-menu.rc @@ -0,0 +1,60 @@ +CheckRecursion MENU { + POPUP "A" { + POPUP "B" { + MENUITEM "a", 1 + MENUITEM "b", 2 + MENUITEM "c", 3 + POPUP "C" { + POPUP "D" { POPUP "E" { POPUP "F" { POPUP "G" { POPUP "H" { POPUP "I" { + MENUITEM "d", 57134 + }}}}}} + } + } + MENUITEM "efg", 23333 + } +} + + +CheckFlags MENU { + MENUITEM "a", 1, CHECKED + MENUITEM "b", 2, GRAYED + MENUITEM "c", 3, HELP + MENUITEM "d", 4, INACTIVE + MENUITEM "e", 5, MENUBARBREAK + MENUITEM "f", 6, MENUBREAK + MENUITEM "ad", 7, CHECKED, INACTIVE + MENUITEM SEPARATOR + POPUP "A", CHECKED { MENUITEM "x", 100 } + POPUP "B", GRAYED { MENUITEM "x", 101 } + POPUP "C", HELP { MENUITEM "x", 102 } + POPUP "D", INACTIVE { MENUITEM "x", 103 } + POPUP "E", MENUBARBREAK { MENUITEM "x", 104 } + POPUP "F", MENUBREAK { MENUITEM "x", 105 } + POPUP "G", HELP, MENUBARBREAK, GRAYED { + POPUP "H", CHECKED, MENUBREAK, HELP, INACTIVE { + MENUITEM SEPARATOR + MENUITEM "x", 106, INACTIVE, MENUBARBREAK + MENUITEM SEPARATOR + } + } + MENUITEM "abcdef", 8, help, inactive, menubarbreak, checked, grayed, menubreak +} + + +CheckOpts MENU +CHARACTERISTICS 500 +LANGUAGE 1, 1 +VERSION 128 +BEGIN + POPUP "&Only separator" { + MENUITEM SEPARATOR + } + POPUP "O&ther things" { + MENUITEM "&abcde", 1 + MENUITEM "a&bcde", 2 + MENUITEM "ab&cde", 3 + MENUITEM "abc&de", 4 + MENUITEM "abcd&e", 5 + } +END + Index: llvm/test/tools/llvm-rc/tag-menu.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/tag-menu.test @@ -0,0 +1,75 @@ +; RUN: llvm-rc /FO %t %p/Inputs/tag-menu.rc +; RUN: llvm-readobj %t | FileCheck %s --check-prefix=MENU + +; MENU: Resource type (int): 4 +; MENU-NEXT: Resource name (string): CHECKRECURSION +; MENU-NEXT: Data version: 0 +; MENU-NEXT: Memory flags: 0x1030 +; MENU-NEXT: Language ID: 1033 +; MENU-NEXT: Version (major): 0 +; MENU-NEXT: Version (minor): 0 +; MENU-NEXT: Characteristics: 0 +; MENU-NEXT: Data size: 102 +; MENU-NEXT: Data: ( +; MENU-NEXT: 0000: 00000000 90004100 00001000 42000000 |......A.....B...| +; MENU-NEXT: 0010: 00000100 61000000 00000200 62000000 |....a.......b...| +; MENU-NEXT: 0020: 00000300 63000000 90004300 00009000 |....c.....C.....| +; MENU-NEXT: 0030: 44000000 90004500 00009000 46000000 |D.....E.....F...| +; MENU-NEXT: 0040: 90004700 00009000 48000000 90004900 |..G.....H.....I.| +; MENU-NEXT: 0050: 00008000 2EDF6400 00008000 255B6500 |......d.....%[e.| +; MENU-NEXT: 0060: 66006700 0000 |f.g...| +; MENU-NEXT: ) + +; MENU-DAG: Resource type (int): 4 +; MENU-NEXT: Resource name (string): CHECKFLAGS +; MENU-NEXT: Data version: 0 +; MENU-NEXT: Memory flags: 0x1030 +; MENU-NEXT: Language ID: 1033 +; MENU-NEXT: Version (major): 0 +; MENU-NEXT: Version (minor): 0 +; MENU-NEXT: Characteristics: 0 +; MENU-NEXT: Data size: 202 +; MENU-NEXT: Data: ( +; MENU-NEXT: 0000: 00000000 08000100 61000000 01000200 |........a.......| +; MENU-NEXT: 0010: 62000000 00400300 63000000 02000400 |b....@..c.......| +; MENU-NEXT: 0020: 64000000 20000500 65000000 40000600 |d... ...e...@...| +; MENU-NEXT: 0030: 66000000 0A000700 61006400 00000000 |f.......a.d.....| +; MENU-NEXT: 0040: 00000000 18004100 00008000 64007800 |......A.....d.x.| +; MENU-NEXT: 0050: 00001100 42000000 80006500 78000000 |....B.....e.x...| +; MENU-NEXT: 0060: 10404300 00008000 66007800 00001200 |.@C.....f.x.....| +; MENU-NEXT: 0070: 44000000 80006700 78000000 30004500 |D.....g.x...0.E.| +; MENU-NEXT: 0080: 00008000 68007800 00005000 46000000 |....h.x...P.F...| +; MENU-NEXT: 0090: 80006900 78000000 31404700 0000DA40 |..i.x...1@G....@| +; MENU-NEXT: 00A0: 48000000 00000000 00002200 6A007800 |H.........".j.x.| +; MENU-NEXT: 00B0: 00008000 00000000 EB400800 61006200 |.........@..a.b.| +; MENU-NEXT: 00C0: 63006400 65006600 0000 |c.d.e.f...| +; MENU-NEXT: ) + +; MENU-DAG: Resource type (int): 4 +; MENU-NEXT: Resource name (string): CHECKOPTS +; MENU-NEXT: Data version: 0 +; MENU-NEXT: Memory flags: 0x1030 +; MENU-NEXT: Language ID: 1025 +; MENU-NEXT: Version (major): 0 +; MENU-NEXT: Version (minor): 128 +; MENU-NEXT: Characteristics: 500 +; MENU-NEXT: Data size: 164 +; MENU-NEXT: Data: ( +; MENU-NEXT: 0000: 00000000 10002600 4F006E00 6C007900 |......&.O.n.l.y.| +; MENU-NEXT: 0010: 20007300 65007000 61007200 61007400 | .s.e.p.a.r.a.t.| +; MENU-NEXT: 0020: 6F007200 00008000 00000000 90004F00 |o.r...........O.| +; MENU-NEXT: 0030: 26007400 68006500 72002000 74006800 |&.t.h.e.r. .t.h.| +; MENU-NEXT: 0040: 69006E00 67007300 00000000 01002600 |i.n.g.s.......&.| +; MENU-NEXT: 0050: 61006200 63006400 65000000 00000200 |a.b.c.d.e.......| +; MENU-NEXT: 0060: 61002600 62006300 64006500 00000000 |a.&.b.c.d.e.....| +; MENU-NEXT: 0070: 03006100 62002600 63006400 65000000 |..a.b.&.c.d.e...| +; MENU-NEXT: 0080: 00000400 61006200 63002600 64006500 |....a.b.c.&.d.e.| +; MENU-NEXT: 0090: 00008000 05006100 62006300 64002600 |......a.b.c.d.&.| +; MENU-NEXT: 00A0: 65000000 |e...| +; MENU-NEXT: ) + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-menu-bad-menuitem-id.rc 2>&1 | FileCheck %s --check-prefix BADID + +; BADID: llvm-rc: Error in MENU statement (ID 1): +; BADID-NEXT: MENUITEM action ID (100000) does not fit in 16 bits. Index: llvm/tools/llvm-rc/ResourceFileWriter.h =================================================================== --- llvm/tools/llvm-rc/ResourceFileWriter.h +++ llvm/tools/llvm-rc/ResourceFileWriter.h @@ -32,6 +32,7 @@ Error visitNullResource(const RCResource *) override; Error visitAcceleratorsResource(const RCResource *) override; Error visitHTMLResource(const RCResource *) override; + Error visitMenuResource(const RCResource *) override; Error visitCharacteristicsStmt(const CharacteristicsStmt *) override; Error visitLanguageStmt(const LanguageResource *) override; @@ -63,6 +64,12 @@ // HTMLResource Error writeHTMLBody(const RCResource *); + // MenuResource + Error writeMenuDefinition(const std::unique_ptr &, + uint16_t Flags); + Error writeMenuDefinitionList(const MenuDefinitionList &List); + Error writeMenuBody(const RCResource *); + // Output stream handling. std::unique_ptr FS; Index: llvm/tools/llvm-rc/ResourceFileWriter.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceFileWriter.cpp +++ llvm/tools/llvm-rc/ResourceFileWriter.cpp @@ -205,6 +205,10 @@ return writeResource(Res, &ResourceFileWriter::writeHTMLBody); } +Error ResourceFileWriter::visitMenuResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeMenuBody); +} + Error ResourceFileWriter::visitCharacteristicsStmt( const CharacteristicsStmt *Stmt) { ObjectData.Characteristics = Stmt->Value; @@ -382,5 +386,55 @@ return appendFile(cast(Base)->HTMLLoc); } +// --- MenuResource helpers. --- // + +Error ResourceFileWriter::writeMenuDefinition( + const std::unique_ptr &Def, uint16_t Flags) { + assert(Def); + const MenuDefinition *DefPtr = Def.get(); + + if (auto *MenuItemPtr = dyn_cast(DefPtr)) { + writeInt(Flags); + RETURN_IF_ERROR( + checkNumberFits(MenuItemPtr->Id, "MENUITEM action ID")); + writeInt(MenuItemPtr->Id); + RETURN_IF_ERROR(writeCString(MenuItemPtr->Name)); + return Error::success(); + } + + if (isa(DefPtr)) { + writeInt(Flags); + writeInt(0); + return Error::success(); + } + + auto *PopupPtr = cast(DefPtr); + writeInt(Flags); + RETURN_IF_ERROR(writeCString(PopupPtr->Name)); + return writeMenuDefinitionList(PopupPtr->SubItems); +} + +Error ResourceFileWriter::writeMenuDefinitionList( + const MenuDefinitionList &List) { + for (auto &Def : List.Definitions) { + uint16_t Flags = Def->getResFlags(); + // Last element receives an additional 0x80 flag. + const uint16_t LastElementFlag = 0x0080; + if (&Def == &List.Definitions.back()) + Flags |= LastElementFlag; + + RETURN_IF_ERROR(writeMenuDefinition(Def, Flags)); + } + return Error::success(); +} + +Error ResourceFileWriter::writeMenuBody(const RCResource *Base) { + // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0. + // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx + writeObject(0); + + return writeMenuDefinitionList(cast(Base)->Elements); +} + } // namespace rc } // namespace llvm Index: llvm/tools/llvm-rc/ResourceScriptStmt.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.h +++ llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -318,6 +318,8 @@ MENUBREAK = 0x0040 }; + enum MenuDefKind { MkBase, MkSeparator, MkMenuItem, MkPopup }; + static constexpr size_t NumFlags = 6; static StringRef OptionsStr[NumFlags]; static uint32_t OptionsFlags[NumFlags]; @@ -326,13 +328,16 @@ return OS << "Base menu definition\n"; } virtual ~MenuDefinition() {} + + virtual uint16_t getResFlags() const { return 0; } + virtual MenuDefKind getKind() const { return MkBase; } }; // Recursive description of a whole submenu. class MenuDefinitionList : public MenuDefinition { +public: std::vector> Definitions; -public: void addDefinition(std::unique_ptr Def) { Definitions.push_back(std::move(Def)); } @@ -345,46 +350,73 @@ class MenuSeparator : public MenuDefinition { public: raw_ostream &log(raw_ostream &) const override; + + MenuDefKind getKind() const override { return MkSeparator; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkSeparator; + } }; // MENUITEM statement definition. // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381024(v=vs.85).aspx class MenuItem : public MenuDefinition { +public: StringRef Name; uint32_t Id; uint16_t Flags; -public: MenuItem(StringRef Caption, uint32_t ItemId, uint16_t ItemFlags) : Name(Caption), Id(ItemId), Flags(ItemFlags) {} raw_ostream &log(raw_ostream &) const override; + + uint16_t getResFlags() const override { return Flags; } + MenuDefKind getKind() const override { return MkMenuItem; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkMenuItem; + } }; // POPUP statement definition. // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381030(v=vs.85).aspx class PopupItem : public MenuDefinition { +public: StringRef Name; uint16_t Flags; MenuDefinitionList SubItems; -public: PopupItem(StringRef Caption, uint16_t ItemFlags, MenuDefinitionList &&SubItemsList) : Name(Caption), Flags(ItemFlags), SubItems(std::move(SubItemsList)) {} raw_ostream &log(raw_ostream &) const override; + + // This has an additional (0x10) flag. It doesn't match with documented + // 0x01 flag, though. + uint16_t getResFlags() const override { return Flags | 0x10; } + MenuDefKind getKind() const override { return MkPopup; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkPopup; + } }; // Menu resource definition. class MenuResource : public OptStatementsRCResource { +public: MenuDefinitionList Elements; -public: MenuResource(OptionalStmtList &&OptStmts, MenuDefinitionList &&Items) : OptStatementsRCResource(std::move(OptStmts)), Elements(std::move(Items)) {} raw_ostream &log(raw_ostream &) const override; + + IntOrString getResourceType() const override { return RkMenu; } + Twine getResourceTypeName() const override { return "MENU"; } + Error visit(Visitor *V) const override { return V->visitMenuResource(this); } + ResourceKind getKind() const override { return RkMenu; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkMenu; + } }; // STRINGTABLE resource. Contains a list of strings, each having its unique ID. Index: llvm/tools/llvm-rc/ResourceVisitor.h =================================================================== --- llvm/tools/llvm-rc/ResourceVisitor.h +++ llvm/tools/llvm-rc/ResourceVisitor.h @@ -30,6 +30,7 @@ virtual Error visitNullResource(const RCResource *) = 0; virtual Error visitAcceleratorsResource(const RCResource *) = 0; virtual Error visitHTMLResource(const RCResource *) = 0; + virtual Error visitMenuResource(const RCResource *) = 0; virtual Error visitCharacteristicsStmt(const CharacteristicsStmt *) = 0; virtual Error visitLanguageStmt(const LanguageResource *) = 0;