| 1 | //============================================================================ |
| 2 | // |
| 3 | // SSSS tt lll lll |
| 4 | // SS SS tt ll ll |
| 5 | // SS tttttt eeee ll ll aaaa |
| 6 | // SSSS tt ee ee ll ll aa |
| 7 | // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" |
| 8 | // SS SS tt ee ll ll aa aa |
| 9 | // SSSS ttt eeeee llll llll aaaaa |
| 10 | // |
| 11 | // Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony |
| 12 | // and the Stella Team |
| 13 | // |
| 14 | // See the file "License.txt" for information on usage and redistribution of |
| 15 | // this file, and for a DISCLAIMER OF ALL WARRANTIES. |
| 16 | //============================================================================ |
| 17 | |
| 18 | #include "EventHandler.hxx" |
| 19 | #include "FrameBuffer.hxx" |
| 20 | #include "Dialog.hxx" |
| 21 | #include "FBSurface.hxx" |
| 22 | #include "Font.hxx" |
| 23 | #include "OSystem.hxx" |
| 24 | #include "ControllerDetector.hxx" |
| 25 | #include "Props.hxx" |
| 26 | #include "PNGLibrary.hxx" |
| 27 | #include "Rect.hxx" |
| 28 | #include "Widget.hxx" |
| 29 | #include "TIAConstants.hxx" |
| 30 | #include "RomInfoWidget.hxx" |
| 31 | |
| 32 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 33 | RomInfoWidget::RomInfoWidget(GuiObject* boss, const GUI::Font& font, |
| 34 | int x, int y, int w, int h) |
| 35 | : Widget(boss, font, x, y, w, h), |
| 36 | mySurfaceIsValid(false), |
| 37 | myHaveProperties(false), |
| 38 | myAvail(w > 400 ? |
| 39 | Common::Size(TIAConstants::viewableWidth*2, TIAConstants::viewableHeight*2) : |
| 40 | Common::Size(TIAConstants::viewableWidth, TIAConstants::viewableHeight)) |
| 41 | { |
| 42 | _flags = Widget::FLAG_ENABLED; |
| 43 | _bgcolor = kDlgColor; |
| 44 | _bgcolorlo = kBGColorLo; |
| 45 | } |
| 46 | |
| 47 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 48 | void RomInfoWidget::reloadProperties(const FilesystemNode& node) |
| 49 | { |
| 50 | // The ROM may have changed since we were last in the browser, either |
| 51 | // by saving a different image or through a change in video renderer, |
| 52 | // so we reload the properties |
| 53 | if(myHaveProperties) |
| 54 | parseProperties(node); |
| 55 | } |
| 56 | |
| 57 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 58 | void RomInfoWidget::setProperties(const Properties& props, const FilesystemNode& node) |
| 59 | { |
| 60 | myHaveProperties = true; |
| 61 | myProperties = props; |
| 62 | |
| 63 | // Decide whether the information should be shown immediately |
| 64 | if(instance().eventHandler().state() == EventHandlerState::LAUNCHER) |
| 65 | parseProperties(node); |
| 66 | } |
| 67 | |
| 68 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 69 | void RomInfoWidget::clearProperties() |
| 70 | { |
| 71 | myHaveProperties = mySurfaceIsValid = false; |
| 72 | if(mySurface) |
| 73 | mySurface->setVisible(mySurfaceIsValid); |
| 74 | |
| 75 | // Decide whether the information should be shown immediately |
| 76 | if(instance().eventHandler().state() == EventHandlerState::LAUNCHER) |
| 77 | setDirty(); |
| 78 | } |
| 79 | |
| 80 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 81 | void RomInfoWidget::parseProperties(const FilesystemNode& node) |
| 82 | { |
| 83 | // Check if a surface has ever been created; if so, we use it |
| 84 | // The surface will always be the maximum size, but sometimes we'll |
| 85 | // only draw certain parts of it |
| 86 | if(mySurface == nullptr) |
| 87 | { |
| 88 | mySurface = instance().frameBuffer().allocateSurface( |
| 89 | TIAConstants::viewableWidth*2, TIAConstants::viewableHeight*2); |
| 90 | mySurface->attributes().smoothing = true; |
| 91 | mySurface->applyAttributes(); |
| 92 | |
| 93 | dialog().addSurface(mySurface); |
| 94 | } |
| 95 | |
| 96 | // Initialize to empty properties entry |
| 97 | mySurfaceErrorMsg = "" ; |
| 98 | mySurfaceIsValid = false; |
| 99 | myRomInfo.clear(); |
| 100 | |
| 101 | #ifdef PNG_SUPPORT |
| 102 | // Get a valid filename representing a snapshot file for this rom |
| 103 | const string& filename = instance().snapshotLoadDir() + |
| 104 | myProperties.get(PropType::Cart_Name) + ".png" ; |
| 105 | |
| 106 | // Read the PNG file |
| 107 | try |
| 108 | { |
| 109 | instance().png().loadImage(filename, *mySurface); |
| 110 | |
| 111 | // Scale surface to available image area |
| 112 | const Common::Rect& src = mySurface->srcRect(); |
| 113 | float scale = std::min(float(myAvail.w) / src.w(), float(myAvail.h) / src.h()) * |
| 114 | instance().frameBuffer().hidpiScaleFactor(); |
| 115 | mySurface->setDstSize(uInt32(src.w() * scale), uInt32(src.h() * scale)); |
| 116 | mySurfaceIsValid = true; |
| 117 | } |
| 118 | catch(const runtime_error& e) |
| 119 | { |
| 120 | mySurfaceErrorMsg = e.what(); |
| 121 | } |
| 122 | #else |
| 123 | mySurfaceErrorMsg = "PNG image loading not supported" ; |
| 124 | #endif |
| 125 | if(mySurface) |
| 126 | mySurface->setVisible(mySurfaceIsValid); |
| 127 | |
| 128 | // Now add some info for the message box below the image |
| 129 | myRomInfo.push_back("Name: " + myProperties.get(PropType::Cart_Name)); |
| 130 | myRomInfo.push_back("Manufacturer: " + myProperties.get(PropType::Cart_Manufacturer)); |
| 131 | myRomInfo.push_back("Model: " + myProperties.get(PropType::Cart_ModelNo)); |
| 132 | myRomInfo.push_back("Rarity: " + myProperties.get(PropType::Cart_Rarity)); |
| 133 | myRomInfo.push_back("Note: " + myProperties.get(PropType::Cart_Note)); |
| 134 | bool swappedPorts = myProperties.get(PropType::Console_SwapPorts) == "YES" ; |
| 135 | |
| 136 | // Load the image for controller auto detection |
| 137 | string left = myProperties.get(PropType::Controller_Left); |
| 138 | string right = myProperties.get(PropType::Controller_Right); |
| 139 | Controller::Type leftType = Controller::getType(left); |
| 140 | Controller::Type rightType = Controller::getType(right); |
| 141 | try |
| 142 | { |
| 143 | ByteBuffer image; |
| 144 | string md5 = myProperties.get(PropType::Cart_MD5); |
| 145 | uInt32 size = 0; |
| 146 | |
| 147 | if(node.exists() && !node.isDirectory() && |
| 148 | (image = instance().openROM(node, md5, size)) != nullptr) |
| 149 | { |
| 150 | left = ControllerDetector::detectName(image.get(), size, leftType, |
| 151 | !swappedPorts ? Controller::Jack::Left : Controller::Jack::Right, |
| 152 | instance().settings()); |
| 153 | right = ControllerDetector::detectName(image.get(), size, rightType, |
| 154 | !swappedPorts ? Controller::Jack::Right : Controller::Jack::Left, |
| 155 | instance().settings()); |
| 156 | } |
| 157 | } |
| 158 | catch(const runtime_error&) |
| 159 | { |
| 160 | // Do nothing; we simply don't update the controllers if openROM |
| 161 | // failed for any reason |
| 162 | left = right = "" ; |
| 163 | } |
| 164 | if(left != "" && right != "" ) |
| 165 | myRomInfo.push_back("Controllers: " + (left + " (left), " + right + " (right)" )); |
| 166 | } |
| 167 | |
| 168 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 169 | void RomInfoWidget::drawWidget(bool hilite) |
| 170 | { |
| 171 | FBSurface& s = dialog().surface(); |
| 172 | bool onTop = _boss->dialog().isOnTop(); |
| 173 | |
| 174 | const int yoff = myAvail.h + 10; |
| 175 | |
| 176 | s.fillRect(_x+2, _y+2, _w-4, _h-4, onTop ? _bgcolor : _bgcolorlo); |
| 177 | s.frameRect(_x, _y, _w, _h, kColor); |
| 178 | s.frameRect(_x, _y+yoff, _w, _h-yoff, kColor); |
| 179 | |
| 180 | if(!myHaveProperties) return; |
| 181 | |
| 182 | if(mySurfaceIsValid) |
| 183 | { |
| 184 | const Common::Rect& dst = mySurface->dstRect(); |
| 185 | const uInt32 scale = instance().frameBuffer().hidpiScaleFactor(); |
| 186 | uInt32 x = _x*scale + ((_w*scale - dst.w()) >> 1); |
| 187 | uInt32 y = _y*scale + ((yoff*scale - dst.h()) >> 1); |
| 188 | |
| 189 | // Make sure when positioning the snapshot surface that we take |
| 190 | // the dialog surface position into account |
| 191 | const Common::Rect& s_dst = s.dstRect(); |
| 192 | mySurface->setDstPos(x + s_dst.x(), y + s_dst.y()); |
| 193 | } |
| 194 | else if(mySurfaceErrorMsg != "" ) |
| 195 | { |
| 196 | const GUI::Font& font = instance().frameBuffer().font(); |
| 197 | uInt32 x = _x + ((_w - font.getStringWidth(mySurfaceErrorMsg)) >> 1); |
| 198 | uInt32 y = _y + ((yoff - font.getLineHeight()) >> 1); |
| 199 | s.drawString(font, mySurfaceErrorMsg, x, y, _w - 10, onTop ? _textcolor : _shadowcolor); |
| 200 | } |
| 201 | |
| 202 | int xpos = _x + 8, ypos = _y + yoff + 10; |
| 203 | for(const auto& info: myRomInfo) |
| 204 | { |
| 205 | int lines = s.drawString(_font, info, xpos, ypos, _w - 16, _font.getFontHeight() * 3, |
| 206 | onTop ? _textcolor : _shadowcolor); |
| 207 | ypos += _font.getLineHeight() + (lines - 1) * _font.getFontHeight(); |
| 208 | } |
| 209 | } |
| 210 | |