/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2010 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "common/PrecompiledHeader.h" #include "common/wxGuiTools.h" #include "common/pxStaticText.h" #include #include #include const pxAlignmentType pxCentre = {pxAlignmentType::Center}, // Horizontal centered alignment pxCenter = pxCentre, pxMiddle = {pxAlignmentType::Middle}, // vertical centered alignment pxAlignLeft = {pxAlignmentType::Left}, pxAlignRight = {pxAlignmentType::Right}, pxAlignTop = {pxAlignmentType::Top}, pxAlignBottom = {pxAlignmentType::Bottom}; const pxStretchType pxShrink = {pxStretchType::Shrink}, pxExpand = {pxStretchType::Expand}, pxShaped = {pxStretchType::Shaped}, pxReserveHidden = {pxStretchType::ReserveHidden}, pxFixedMinimum = {pxStretchType::FixedMinimum}; wxSizerFlags pxAlignmentType::Apply(wxSizerFlags flags) const { switch (intval) { case Centre: flags.Align(flags.GetFlags() | wxALIGN_CENTRE_HORIZONTAL); break; case Middle: flags.Align(flags.GetFlags() | wxALIGN_CENTRE_VERTICAL); break; case Left: flags.Left(); break; case Right: flags.Right(); break; case Top: flags.Top(); break; case Bottom: flags.Bottom(); break; } return flags; } wxSizerFlags pxStretchType::Apply(wxSizerFlags flags) const { switch (intval) { case Shrink: //pxFail( "wxSHRINK is an ignored stretch flag." ); break; case Expand: flags.Expand(); break; case Shaped: flags.Shaped(); break; case ReserveHidden: flags.ReserveSpaceEvenIfHidden(); break; case FixedMinimum: flags.FixedMinSize(); break; //case Tile: // pxAssert( "pxTile is an unsupported stretch tag (ignored)." ); //break; } return flags; } wxSizerFlags operator&(const wxSizerFlags& _flgs, const wxSizerFlags& _flgs2) { //return align.Apply( _flgs ); wxSizerFlags retval; uint allflags = (_flgs.GetFlags() | _flgs2.GetFlags()); retval.Align(allflags & wxALIGN_MASK); if (allflags & wxEXPAND) retval.Expand(); if (allflags & wxSHAPED) retval.Shaped(); if (allflags & wxFIXED_MINSIZE) retval.FixedMinSize(); if (allflags & wxRESERVE_SPACE_EVEN_IF_HIDDEN) retval.ReserveSpaceEvenIfHidden(); // Compounding borders is probably a fair approach: retval.Border(allflags & wxALL, _flgs.GetBorderInPixels() + _flgs2.GetBorderInPixels()); // Compounding proportions works as well, I figure. retval.Proportion(_flgs.GetProportion() + _flgs2.GetProportion()); return retval; } // ---------------------------------------------------------------------------- // Reference/Handle versions! void operator+=(wxSizer& target, wxWindow* src) { target.Add(src); } void operator+=(wxSizer& target, wxSizer* src) { target.Add(src); } void operator+=(wxSizer& target, wxWindow& src) { target.Add(&src); } void operator+=(wxSizer& target, wxSizer& src) { target.Add(&src); } void operator+=(wxSizer& target, int spacer) { target.AddSpacer(spacer); } void operator+=(wxSizer& target, const pxStretchSpacer& spacer) { target.AddStretchSpacer(spacer.proportion); } void operator+=(wxWindow& target, int spacer) { if (!pxAssert(target.GetSizer() != NULL)) return; target.GetSizer()->AddSpacer(spacer); } void operator+=(wxWindow& target, const pxStretchSpacer& spacer) { if (!pxAssert(target.GetSizer() != NULL)) return; target.GetSizer()->AddStretchSpacer(spacer.proportion); } // ---------------------------------------------------------------------------- // Pointer versions! (note that C++ requires one of the two operator params be a // "poper" object type (non-pointer), so that's why there's only a couple of these. void operator+=(wxSizer* target, wxWindow& src) { if (!pxAssert(target != NULL)) return; target->Add(&src); } void operator+=(wxSizer* target, wxSizer& src) { if (!pxAssert(target != NULL)) return; target->Add(&src); } // ---------------------------------------------------------------------------- // Returns FALSE if the window position is considered invalid, which means that it's title // bar is most likely not easily grabble. Such a window should be moved to a valid or // default position. bool pxIsValidWindowPosition(const wxWindow& window, const wxPoint& windowPos) { // The height of the window is only revlevant to the height of a title bar, which is // all we need visible for the user to be able to drag the window into view. But // there's no way to get that info from wx, so we'll just have to guesstimate... const wxSize sizeMatters(window.GetSize().GetWidth(), 32); for (unsigned int i = 0; i < wxDisplay::GetCount(); i++) { const auto rect = wxDisplay(i).GetGeometry(); if (rect.Contains(wxRect(windowPos, sizeMatters))) return true; } return false; } // Retrieves the area of the screen, which can be used to enforce a valid zone for // window positioning. (top/left coords are almost always (0,0) and bottom/right // is the resolution of the desktop). wxRect wxGetDisplayArea() { return wxRect(wxPoint(), wxGetDisplaySize()); } // -------------------------------------------------------------------------------------- // pxSizerFlags // -------------------------------------------------------------------------------------- // FlagsAccessors - Provides read-write copies of standard sizer flags for our interface. // These standard definitions provide a consistent and pretty interface for our GUI. // Without them things look compacted, misaligned, and yucky! // // Implementation Note: Accessors are all provisioned as dynamic (realtime) sizer calculations. // I've preferred this over cstatic const variables on the premise that spacing logic could // in the future become a dynamic value (currently it is affixed to 6 for most items). // wxSizerFlags pxSizerFlags::StdSpace() { return wxSizerFlags().Border(wxALL, StdPadding); } wxSizerFlags pxSizerFlags::StdCenter() { return wxSizerFlags().Align(wxALIGN_CENTER).DoubleBorder(); } wxSizerFlags pxSizerFlags::StdExpand() { return StdSpace().Expand(); } // A good sizer flags setting for top-level static boxes or top-level picture boxes. // Gives a generous border to the left, right, and bottom. Top border can be configured // manually by using a spacer. wxSizerFlags pxSizerFlags::TopLevelBox() { return pxBorder(wxLEFT | wxBOTTOM | wxRIGHT, StdPadding).Expand(); } // Flags intended for use on grouped StaticBox controls. These flags are ideal for // StaticBoxes that are part of sub-panels or children of other static boxes, but may // not be best for parent StaticBoxes on dialogs (left and right borders feel a bit // "tight"). wxSizerFlags pxSizerFlags::SubGroup() { // Groups look better with a slightly smaller margin than standard. // (basically this accounts for the group's frame) return pxBorder(wxLEFT | wxBOTTOM | wxRIGHT, StdPadding - 2).Expand(); } // This force-aligns the std button sizer to the right, where (at least) us win32 platform // users always expect it to be. Most likely Mac platforms expect it on the left side // just because it's *not* where win32 sticks it. Too bad! wxSizerFlags pxSizerFlags::StdButton() { return pxBorder().Align(wxALIGN_RIGHT); } wxSizerFlags pxSizerFlags::Checkbox() { return StdExpand(); } void pxSizerFlags::SetBestPadding() { if (wxSystemSettings::GetMetric(wxSYS_SCREEN_X) > 1024 && wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) > 864) StdPadding = 4; else StdPadding = 1; } // -------------------------------------------------------------------------------------- // pxTextWrapper / pxTextWrapperBase (implementations) // -------------------------------------------------------------------------------------- static bool is_cjk_char(const uint ch) { /** * You can check these range at http://unicode.org/charts/ * see the "East Asian Scripts" part. * Notice that not all characters in that part is still in use today, so don't list them all here. */ // FIXME: add range from Japanese-specific and Korean-specific section if you know the // characters are used today. if (ch < 0x2e80) return false; // shortcut for common non-CJK return // Han Ideographs: all except Supplement (ch >= 0x4e00 && ch < 0x9fcf) || (ch >= 0x3400 && ch < 0x4dbf) || (ch >= 0x20000 && ch < 0x2a6df) || (ch >= 0xf900 && ch < 0xfaff) || (ch >= 0x3190 && ch < 0x319f) || // Radicals: all except Ideographic Description (ch >= 0x2e80 && ch < 0x2eff) || (ch >= 0x2f00 && ch < 0x2fdf) || (ch >= 0x31c0 && ch < 0x31ef) || // Chinese-specific: Bopomofo (ch >= 0x3000 && ch < 0x303f) || // Japanese-specific: Halfwidth Katakana (ch >= 0xff00 && ch < 0xffef) || // Japanese-specific: Hiragana, Katakana (ch >= 0x3040 && ch <= 0x309f) || (ch >= 0x30a0 && ch <= 0x30ff) || // Korean-specific: Hangul Syllables, Halfwidth Jamo (ch >= 0xac00 && ch < 0xd7af) || (ch >= 0xff00 && ch < 0xffef); } /* * According to Kinsoku-Shori, Japanese rules about line-breaking: * * * the following characters cannot begin a line (so we will never break before them): * 、。,.)〕]}〉》」』】’”ゝゞヽヾ々?!:;ぁぃぅぇぉゃゅょゎァィゥェォャュョヮっヵッヶ・…ー * * * the following characters cannot end a line (so we will never break after them): * (〔[{〈《「『【‘“ * * Unicode range that concerns word wrap for Chinese: * 全角ASCII、全角中英文标点 (Fullwidth Character for ASCII, English punctuations and part of Chinese punctuations) * http://www.unicode.org/charts/PDF/UFF00.pdf * CJK 标点符号 (CJK punctuations) * http://www.unicode.org/charts/PDF/U3000.pdf */ static bool no_break_after(const uint ch) { // clang-format off switch (ch) { /** * don't break after these Japanese/Chinese characters */ case 0x2018: case 0x201c: case 0x3008: case 0x300a: case 0x300c: case 0x300e: case 0x3010: case 0x3014: case 0x3016: case 0x301a: case 0x301d: case 0xff08: case 0xff3b: case 0xff5b: /** * FIXME don't break after these Korean characters */ return true; } // clang-format on return false; } static bool no_break_before(const uint ch) { // clang-format off switch (ch) { /** * don't break before these Japanese characters */ case 0x2019: case 0x201d: case 0x2026: case 0x3001: case 0x3002: case 0x3005: case 0x3009: case 0x300b: case 0x300d: case 0x300f: case 0x301c: case 0x3011: case 0x3015: case 0x3017: case 0x301b: case 0x301e: case 0x3041: case 0x3043: case 0x3045: case 0x3047: case 0x3049: case 0x3063: case 0x3083: case 0x3085: case 0x3087: case 0x308e: case 0x309d: case 0x309e: case 0x30a1: case 0x30a3: case 0x30a5: case 0x30a7: case 0x30a9: case 0x30c3: case 0x30e3: case 0x30e5: case 0x30e7: case 0x30ee: case 0x30f5: case 0x30f6: case 0x30fb: case 0x30fc: case 0x30fd: case 0x30fe: case 0xff01: case 0xff09: case 0xff0c: case 0xff0d: case 0xff0e: case 0xff1a: case 0xff1b: case 0xff1f: case 0xff3d: case 0xff5d: case 0xff64: case 0xff65: /** * FIXME don't break before these Korean characters */ /** * don't break before these Chinese characters * contains * many Chinese punctuations that should not start a line * and right side of different kinds of brackets, quotes */ return true; } // clang-format on return false; } pxTextWrapperBase& pxTextWrapperBase::Wrap(const wxWindow& win, const wxString& text, int widthMax) { if (text.empty()) return *this; bool wasWrapped = false; wxString line; line.reserve(text.length() + 12); wxString::const_iterator lastSpace = text.end(); wxString::const_iterator lineStart = text.begin(); for (wxString::const_iterator p = lineStart;; ++p) { if (IsStartOfNewLine()) { OnNewLine(); lastSpace = text.end(); lineStart = p; if (wasWrapped) line = m_indent; else line.clear(); } if (p == text.end() || *p == L'\n') { wasWrapped = false; DoOutputLine(line); if (p == text.end()) break; } else { // not EOL if (is_cjk_char(*p)) { if (!no_break_before(*p)) { if (p == lineStart || !no_break_after(*(p - 1))) lastSpace = p; } } else if (*p == L' ' || *p == L',' || *p == L'/') lastSpace = p; line += *p; if (widthMax >= 0 && lastSpace != text.end()) { int width; win.GetTextExtent(line, &width, NULL); if (width > widthMax) { wasWrapped = true; // remove the last word from this line line.erase(lastSpace - lineStart, p + 1 - lineStart); DoOutputLine(line); // go back to the last word of this line which we didn't // output yet p = lastSpace; if (*p != L' ') p--; } } //else: no wrapping at all or impossible to wrap } } return *this; } void pxTextWrapperBase::DoOutputLine(const wxString& line) { OnOutputLine(line); m_linecount++; m_eol = true; } // this function is a destructive inspector: when it returns true it also // resets the flag to false so calling it again wouldn't return true any // more bool pxTextWrapperBase::IsStartOfNewLine() { if (!m_eol) return false; m_eol = false; return true; } pxTextWrapper& pxTextWrapper::Wrap(const wxWindow& win, const wxString& text, int widthMax) { _parent::Wrap(win, text, widthMax); return *this; } pxTextWrapper& pxTextWrapper::Wrap(const wxWindow* win, const wxString& text, int widthMax) { if (win) _parent::Wrap(*win, text, widthMax); return *this; } void pxTextWrapper::OnOutputLine(const wxString& line) { m_text += line; } void pxTextWrapper::OnNewLine() { m_text += L'\n'; } // -------------------------------------------------------------------------------------- // ScopedBusyCursor (implementations) // -------------------------------------------------------------------------------------- std::stack ScopedBusyCursor::m_cursorStack; BusyCursorType ScopedBusyCursor::m_defBusyType; ScopedBusyCursor::ScopedBusyCursor(BusyCursorType busytype) { pxAssert(wxTheApp != NULL); BusyCursorType curtype = Cursor_NotBusy; if (!m_cursorStack.empty()) curtype = m_cursorStack.top(); if (curtype < busytype) SetManualBusyCursor(curtype = busytype); m_cursorStack.push(curtype); } ScopedBusyCursor::~ScopedBusyCursor() { if (!pxAssert(wxTheApp != NULL)) return; if (!pxAssert(!m_cursorStack.empty())) { SetManualBusyCursor(m_defBusyType); return; } BusyCursorType curtype = m_cursorStack.top(); m_cursorStack.pop(); if (m_cursorStack.empty()) SetManualBusyCursor(m_defBusyType); else if (m_cursorStack.top() != curtype) SetManualBusyCursor(m_cursorStack.top()); } void ScopedBusyCursor::SetDefault(BusyCursorType busytype) { if (busytype == m_defBusyType) return; m_defBusyType = busytype; if (m_cursorStack.empty()) SetManualBusyCursor(busytype); } void ScopedBusyCursor::SetManualBusyCursor(BusyCursorType busytype) { switch (busytype) { case Cursor_NotBusy: wxSetCursor(wxNullCursor); break; case Cursor_KindaBusy: wxSetCursor(StockCursors.GetArrowWait()); break; case Cursor_ReallyBusy: wxSetCursor(*wxHOURGLASS_CURSOR); break; } } const wxCursor& MoreStockCursors::GetArrowWait() { if (!m_arrowWait) m_arrowWait = std::make_unique(wxCURSOR_ARROWWAIT); return *m_arrowWait; } MoreStockCursors StockCursors; // -------------------------------------------------------------------------------------- // pxSetToolTip // -------------------------------------------------------------------------------------- // This is the preferred way to assign tooltips to wxWidgets-based objects. On Windows it // extends the tooltip time to the maximum possible. GTK seems to have indefinite // tooltips, I don't know about OS X. void pxSetToolTip(wxWindow* wind, const wxString& src) { if (wind == NULL) return; // Silently ignore nulls wind->SetToolTip(src); // Make tooltips show for as long as possible on Windows. Linux (GTK) can // show tooltips indefinitely. #ifdef __WXMSW__ wind->GetToolTip()->SetAutoPop(32767); #endif } void pxSetToolTip(wxWindow& wind, const wxString& src) { pxSetToolTip(&wind, src); } wxFont pxGetFixedFont(int ptsize, wxFontWeight weight, bool underline) { return wxFont( ptsize, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, weight, underline #ifdef __WXMSW__ , L"Lucida Console" // better than courier new (win32 only) #endif ); } wxString pxGetAppName() { pxAssert(wxTheApp); return wxTheApp->GetAppName(); }