From 696b6990eafd00e0d947814b98fafa0d27b1e4ee Mon Sep 17 00:00:00 2001 From: translators Date: Mon, 1 Feb 2021 18:17:06 +0000 Subject: [PATCH 001/122] Update: Translations from eints estonian: 110 changes by siimsoni --- src/lang/estonian.txt | 222 +++++++++++++++++++++--------------------- 1 file changed, 111 insertions(+), 111 deletions(-) diff --git a/src/lang/estonian.txt b/src/lang/estonian.txt index 0761d8695d..87ac6c8692 100644 --- a/src/lang/estonian.txt +++ b/src/lang/estonian.txt @@ -386,7 +386,7 @@ STR_TOOLBAR_TOOLTIP_FORWARD :{BLACK}Kiirenda STR_TOOLBAR_TOOLTIP_OPTIONS :{BLACK}Seadistus STR_TOOLBAR_TOOLTIP_SAVE_GAME_ABANDON_GAME :{BLACK}Mängu salvestamine/jätkamine, mängust lahkumine, väljumine STR_TOOLBAR_TOOLTIP_DISPLAY_MAP :{BLACK}Ava kaart, vaateaken või siltide loend -STR_TOOLBAR_TOOLTIP_DISPLAY_TOWN_DIRECTORY :{BLACK}Ava linnade register +STR_TOOLBAR_TOOLTIP_DISPLAY_TOWN_DIRECTORY :{BLACK}Ava asustuste register STR_TOOLBAR_TOOLTIP_DISPLAY_SUBSIDIES :{BLACK}Ava toetuste loend STR_TOOLBAR_TOOLTIP_DISPLAY_LIST_OF_COMPANY_STATIONS :{BLACK}Avab ettevõtte jaamade registri STR_TOOLBAR_TOOLTIP_DISPLAY_COMPANY_FINANCES :{BLACK}Ava ettevõtte rahavoogude aruanne @@ -420,9 +420,9 @@ STR_SCENEDIT_TOOLBAR_SCENARIO_EDITOR :{YELLOW}Stsenaa STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_BACKWARD :{BLACK}Alguse liigutamine 1 aasta võrra tagasi STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_FORWARD :{BLACK}Alguse liigutamine 1 aasta võrra edasi STR_SCENEDIT_TOOLBAR_TOOLTIP_SET_DATE :{BLACK}Alustamise aasta valikuks klõpsi siin -STR_SCENEDIT_TOOLBAR_TOOLTIP_DISPLAY_MAP_TOWN_DIRECTORY :{BLACK}Ava kaart, linnade register +STR_SCENEDIT_TOOLBAR_TOOLTIP_DISPLAY_MAP_TOWN_DIRECTORY :{BLACK}Ava kaart, asustuste register STR_SCENEDIT_TOOLBAR_LANDSCAPE_GENERATION :{BLACK}Maastiku tekitamine -STR_SCENEDIT_TOOLBAR_TOWN_GENERATION :{BLACK}Linnade tekitamine +STR_SCENEDIT_TOOLBAR_TOWN_GENERATION :{BLACK}Asustuste tekitamine STR_SCENEDIT_TOOLBAR_INDUSTRY_GENERATION :{BLACK}Tööstuste tekitamine STR_SCENEDIT_TOOLBAR_ROAD_CONSTRUCTION :{BLACK}Maanteede ehitamine STR_SCENEDIT_TOOLBAR_TRAM_CONSTRUCTION :{BLACK}Trammiteede ehitamine @@ -446,7 +446,7 @@ STR_SETTINGS_MENU_CONFIG_SETTINGS_TREE :Seaded STR_SETTINGS_MENU_SCRIPT_SETTINGS :AI/GameScripti seaded STR_SETTINGS_MENU_NEWGRF_SETTINGS :NewGRF-i seadistus STR_SETTINGS_MENU_TRANSPARENCY_OPTIONS :Läbipaistvuse seadistus -STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED :Näidatavad linnanimed +STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED :Näidatavad asustuste nimed STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED :Näidatavad jaamanimed STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED :Näidatavad meldepunktid STR_SETTINGS_MENU_SIGNS_DISPLAYED :Näidatavad sildid @@ -472,8 +472,8 @@ STR_MAP_MENU_LINGRAPH_LEGEND :Kaubavoo legend STR_MAP_MENU_SIGN_LIST :Siltide register ############ range for town menu starts -STR_TOWN_MENU_TOWN_DIRECTORY :Linnade register -STR_TOWN_MENU_FOUND_TOWN :Raja linn +STR_TOWN_MENU_TOWN_DIRECTORY :Asustuste register +STR_TOWN_MENU_FOUND_TOWN :Raja asustus ############ range ends here ############ range for subsidies menu starts @@ -810,12 +810,12 @@ STR_SMALLMAP_LEGENDA_TREES :{TINY_FONT}{BLA STR_SMALLMAP_LEGENDA_ROCKS :{TINY_FONT}{BLACK}Kivid STR_SMALLMAP_LEGENDA_WATER :{TINY_FONT}{BLACK}Vesi STR_SMALLMAP_LEGENDA_NO_OWNER :{TINY_FONT}{BLACK}Omanikuta -STR_SMALLMAP_LEGENDA_TOWNS :{TINY_FONT}{BLACK}Linnad +STR_SMALLMAP_LEGENDA_TOWNS :{TINY_FONT}{BLACK}Asustused STR_SMALLMAP_LEGENDA_INDUSTRIES :{TINY_FONT}{BLACK}Tööstused STR_SMALLMAP_LEGENDA_DESERT :{TINY_FONT}{BLACK}Kõrb STR_SMALLMAP_LEGENDA_SNOW :{TINY_FONT}{BLACK}Lumi -STR_SMALLMAP_TOOLTIP_TOGGLE_TOWN_NAMES_ON_OFF :{BLACK}Linnanimede sisse- ja välja lülitamine +STR_SMALLMAP_TOOLTIP_TOGGLE_TOWN_NAMES_ON_OFF :{BLACK}Asustuste nimede sisse- ja välja lülitamine STR_SMALLMAP_CENTER :{BLACK}Näita väikekaardil praegust asukohta STR_SMALLMAP_INDUSTRY :{TINY_FONT}{STRING} ({NUM}) STR_SMALLMAP_LINKSTATS :{TINY_FONT}{STRING} @@ -867,7 +867,7 @@ STR_NEWS_DISASTER_SMALL_UFO :{BIG_FONT}{BLAC STR_NEWS_DISASTER_AIRPLANE_OIL_REFINERY :{BIG_FONT}{BLACK}Naftatöötlustehase plahvatus {TOWN} lähedal! STR_NEWS_DISASTER_HELICOPTER_FACTORY :{BIG_FONT}{BLACK}Tehas purunes teadmata põhjustel {TOWN} lähedal! STR_NEWS_DISASTER_BIG_UFO :{BIG_FONT}{BLACK}'UFO' maandus {TOWN} lähedal! -STR_NEWS_DISASTER_COAL_MINE_SUBSIDENCE :{BIG_FONT}{BLACK}Söekaevanduse vajumine jättis endast linna {TOWN} lähedal purustused! +STR_NEWS_DISASTER_COAL_MINE_SUBSIDENCE :{BIG_FONT}{BLACK}Söekaevanduse vajumine jättis endast asustuse {TOWN} lähedal purustused! STR_NEWS_DISASTER_FLOOD_VEHICLE :{BIG_FONT}{BLACK}Üleujutused!{}Vähemalt {COMMA} kadunut arvatakse surnuks peale olulist üleujutust! STR_NEWS_COMPANY_IN_TROUBLE_TITLE :{BIG_FONT}{BLACK}Ettevõttel on probleeme! @@ -877,15 +877,15 @@ STR_NEWS_COMPANY_MERGER_DESCRIPTION :{BIG_FONT}{BLAC STR_NEWS_COMPANY_BANKRUPT_TITLE :{BIG_FONT}{BLACK}Pankrot! STR_NEWS_COMPANY_BANKRUPT_DESCRIPTION :{BIG_FONT}{BLACK}{STRING} suleti asutajate poolt ja kõik varad müüakse maha! STR_NEWS_COMPANY_LAUNCH_TITLE :{BIG_FONT}{BLACK}Loodi uus ettevõte! -STR_NEWS_COMPANY_LAUNCH_DESCRIPTION :{BIG_FONT}{BLACK}Ettevõte {STRING} alustas linna {TOWN} lähedal ehitustöid! +STR_NEWS_COMPANY_LAUNCH_DESCRIPTION :{BIG_FONT}{BLACK}Ettevõte {STRING} alustas asustuse {TOWN} lähedal ehitustöid! STR_NEWS_MERGER_TAKEOVER_TITLE :{BIG_FONT}{BLACK}{STRING} võeti üle ettevõtte {STRING} poolt! STR_PRESIDENT_NAME_MANAGER :{BLACK}{PRESIDENT_NAME}{}(President) -STR_NEWS_NEW_TOWN :{BLACK}{BIG_FONT}{STRING} rahastas uue linna {TOWN} rajamist! +STR_NEWS_NEW_TOWN :{BLACK}{BIG_FONT}{STRING} rahastas uue asustuse {TOWN} rajamist! STR_NEWS_NEW_TOWN_UNSPONSORED :{BLACK}{BIG_FONT}Uus linn nimega {TOWN} on asutatud! -STR_NEWS_INDUSTRY_CONSTRUCTION :{BIG_FONT}{BLACK}Uus {STRING} on linna {TOWN} lähedal ehitamisel! -STR_NEWS_INDUSTRY_PLANTED :{BIG_FONT}{BLACK}Uus {STRING} on istutatud linna {TOWN} lähedale! +STR_NEWS_INDUSTRY_CONSTRUCTION :{BIG_FONT}{BLACK}Uus {STRING} on asustuse {TOWN} lähedal ehitamisel! +STR_NEWS_INDUSTRY_PLANTED :{BIG_FONT}{BLACK}Uus {STRING} on istutatud asustuse {TOWN} lähedale! STR_NEWS_INDUSTRY_CLOSURE_GENERAL :{BIG_FONT}{BLACK}{STRING} juhtkond teatab sulgemisest! STR_NEWS_INDUSTRY_CLOSURE_SUPPLY_PROBLEMS :{BIG_FONT}{BLACK}{STRING} teatab sulgemisest varustusprobleemide tõttu! @@ -940,15 +940,15 @@ STR_NEWS_STATION_NOW_ACCEPTS_CARGO_AND_CARGO :{WHITE}Jaam {ST STR_NEWS_OFFER_OF_SUBSIDY_EXPIRED :{BIG_FONT}{BLACK}Toetuse pakkumine lõppes:{}{}{STRING.g} kohast {STRING} kohta {STRING} veoste eest ei maksta enam toetusi STR_NEWS_SUBSIDY_WITHDRAWN_SERVICE :{BIG_FONT}{BLACK}Toetuse maksmise aeg sai läbi:{}{}{STRING}kohast {STRING} kohta {STRING} eest ei maksta enam edaspidi toetust -STR_NEWS_SERVICE_SUBSIDY_OFFERED :{BIG_FONT}{BLACK}Veoteenusele pakutakse toetust:{}{}{STRING.g}veo eest linnast {STRING} linna {STRING}. Esimesele teenusepakkujale makstakse aasta läbi toetusi! +STR_NEWS_SERVICE_SUBSIDY_OFFERED :{BIG_FONT}{BLACK}Veoteenusele pakutakse toetust:{}{}{STRING.g}veo eest asustusest {STRING} asustusse {STRING}. Esimesele teenusepakkujale makstakse aasta läbi toetusi! STR_NEWS_SERVICE_SUBSIDY_AWARDED_HALF :{BIG_FONT}{BLACK}Teenusetoetust makstakse ettevõttele {STRING}!{}{}{STRING} kohast {STRING} kohta {STRING} teenuse eest makstakse järgmisel aastal 50% rohkem! STR_NEWS_SERVICE_SUBSIDY_AWARDED_DOUBLE :{BIG_FONT}{BLACK}Teenusetoetust makstakse ettevõttele {STRING}!{}{}{STRING} kohast {STRING} kohta {STRING} teenuse eest makstakse järgmisel aastal kahekordselt! STR_NEWS_SERVICE_SUBSIDY_AWARDED_TRIPLE :{BIG_FONT}{BLACK}Teenusetoetust makstakse ettevõttele {STRING}!{}{}{STRING} kohast {STRING} kohta {STRING} teenuse eest makstakse järgmisel aastal kolmekordselt! STR_NEWS_SERVICE_SUBSIDY_AWARDED_QUADRUPLE :{BIG_FONT}{BLACK}Teenusetoetust makstakse ettevõttele {STRING}!{}{}{STRING} kohast {STRING} kohta {STRING} teenuse eest makstakse järgmisel aastal neljakordselt! -STR_NEWS_ROAD_REBUILDING :{BIG_FONT}{BLACK}Liikluskaos linnas {TOWN}!{}{}Ettevõtte {STRING} poolt rahastatud teedeehitus tekitab 6 kuu jooksul maanteedel liiklushäireid! +STR_NEWS_ROAD_REBUILDING :{BIG_FONT}{BLACK}Liikluskaos asustuses {TOWN}!{}{}Ettevõtte {STRING} poolt rahastatud teede ümberehitus tekitab 6 kuu jooksul maanteedel liiklushäireid! STR_NEWS_EXCLUSIVE_RIGHTS_TITLE :{BIG_FONT}{BLACK}Transpordimonopol! -STR_NEWS_EXCLUSIVE_RIGHTS_DESCRIPTION :{BIG_FONT}{BLACK}Kohalik linnavõim{TOWN} allkirjastab lepingu {STRING} transpordi ainuõiguseks üheks aastaks! +STR_NEWS_EXCLUSIVE_RIGHTS_DESCRIPTION :{BIG_FONT}{BLACK}Asustuse {TOWN} omavalitsus allkirjastab lepingu {STRING} veoste ainuõiguseks üheks aastaks! # Extra view window STR_EXTRA_VIEWPORT_TITLE :{WHITE}Vaateaken {COMMA} @@ -1010,8 +1010,8 @@ STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_TOOLTIP :{BLACK}Vali, ku STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_LEFT :Vasakpoolne liiklus STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_RIGHT :Parempoolne liiklus -STR_GAME_OPTIONS_TOWN_NAMES_FRAME :{BLACK}Linnanimed -STR_GAME_OPTIONS_TOWN_NAMES_DROPDOWN_TOOLTIP :{BLACK}Vali linnanimede stiil +STR_GAME_OPTIONS_TOWN_NAMES_FRAME :{BLACK}Asulate nimed +STR_GAME_OPTIONS_TOWN_NAMES_DROPDOWN_TOOLTIP :{BLACK}Vali asulanimede stiil ############ start of townname region STR_GAME_OPTIONS_TOWN_NAME_ORIGINAL_ENGLISH :Inglise (originaalne) @@ -1244,7 +1244,7 @@ STR_CONFIG_SETTING_TRAIN_REVERSING :Keela rongide STR_CONFIG_SETTING_TRAIN_REVERSING_HELPTEXT :Kui on aktiveeritud, siis ei pööra rongid vahejaamades ringi isegi siis kui ringi pööramisel oleks teekond järgmisse sihtpunkti lühem STR_CONFIG_SETTING_DISASTERS :Katastroofid: {STRING} STR_CONFIG_SETTING_DISASTERS_HELPTEXT :Lülita sisse/välja katastroofid, mis aeg-ajalt hävitavad sõidukeid ja taristut -STR_CONFIG_SETTING_CITY_APPROVAL :Linnavalitusese suhtumine pinnase restruktureerimisse: {STRING} +STR_CONFIG_SETTING_CITY_APPROVAL :Astuse omavalitsuse suhtumine pinnase restruktureerimisse: {STRING} STR_CONFIG_SETTING_CITY_APPROVAL_HELPTEXT :Vali kui suurt mõju tekitavad müra ning keskkonna muudatused ettevõtte mainele ja edasistele ehitustoimingutele antud piirkonnas STR_CONFIG_SETTING_MAX_HEIGHTLEVEL :Maksimaalne mägede kõrgus: {STRING} @@ -1256,8 +1256,8 @@ STR_CONFIG_SETTING_CATCHMENT :Reaalsemad veek STR_CONFIG_SETTING_CATCHMENT_HELPTEXT :Erineva suurusega püüdmisala erinevat tüüpi jaamadele ja lennujaamadele. STR_CONFIG_SETTING_SERVE_NEUTRAL_INDUSTRIES :Ettevõtte jaamad teenindavad ka liidetud, erapooletu jaamaga tööstuseid: {STRING} STR_CONFIG_SETTING_SERVE_NEUTRAL_INDUSTRIES_HELPTEXT :Kui lubatud, saavad liidetud jaamaga tööstuseid (nagu naftaplatvormid) teenindada ka lähedalolevad ettevõttele kuuluvad jaamad. Muidu võivad neid tööstuseid teenindada vaid nendega liidetud jaamad. Lähedale ehitatud ettevõtte jaamad neid teenindada ei saa, ning liidetud jaamad ei teeninda teisi tööstuseid -STR_CONFIG_SETTING_EXTRADYNAMITE :Linnateede, -sildade, -tunnelite jne lammutamine: {STRING} -STR_CONFIG_SETTING_EXTRADYNAMITE_HELPTEXT :Lihtsusta linnale kuuluva taristu ja majade eemaldamist +STR_CONFIG_SETTING_EXTRADYNAMITE :Asulateede, -sildade, -tunnelite jne lammutamine: {STRING} +STR_CONFIG_SETTING_EXTRADYNAMITE_HELPTEXT :Lihtsusta asulatele kuuluva taristu ja hoonete eemaldamist STR_CONFIG_SETTING_TRAIN_LENGTH :Rongi pikkuse ülempiir: {STRING} STR_CONFIG_SETTING_TRAIN_LENGTH_HELPTEXT :Määrab rongide pikkuse ülempiiri STR_CONFIG_SETTING_TILE_LENGTH :{COMMA} ruut{P 0 "" u} @@ -1291,8 +1291,8 @@ STR_CONFIG_SETTING_RAW_INDUSTRY_CONSTRUCTION_METHOD_NORMAL :Nagu kõik tö STR_CONFIG_SETTING_RAW_INDUSTRY_CONSTRUCTION_METHOD_PROSPECTING :Uuringutega STR_CONFIG_SETTING_INDUSTRY_PLATFORM :Tasane ala ümber tehaste: {STRING} STR_CONFIG_SETTING_INDUSTRY_PLATFORM_HELPTEXT :Summa kui palju tasast maad on ümber tehase. See tagab et ruumi jääb saadavale ehitiste jaoks -STR_CONFIG_SETTING_MULTIPINDTOWN :Samalaadsed tööstused ühes linnas: {STRING} -STR_CONFIG_SETTING_MULTIPINDTOWN_HELPTEXT :Tavaliselt, linn ei taha rohkem kui ühte tööstustharu igast tüübist. See seade lubab rohkem kui ühe tööstusharu samasse linna +STR_CONFIG_SETTING_MULTIPINDTOWN :Mitu samalaadset tööstust ühe asula piires: {STRING} +STR_CONFIG_SETTING_MULTIPINDTOWN_HELPTEXT :Tavaliselt asula ei taha rohkem kui ühte tööstustharu igast liigist. Selle seadega lubatakse rohkem kui üks samalaadne tööstus ühe asula piires STR_CONFIG_SETTING_SIGNALSIDE :Signaale näidatakse: {STRING} STR_CONFIG_SETTING_SIGNALSIDE_HELPTEXT :Vali kummale poole rada signaalid paigutatakse STR_CONFIG_SETTING_SIGNALSIDE_LEFT :Vasakul @@ -1314,13 +1314,13 @@ STR_CONFIG_SETTING_AUTOSCROLL_MAIN_VIEWPORT_FULLSCREEN :Põhivaates, ai STR_CONFIG_SETTING_AUTOSCROLL_MAIN_VIEWPORT :Põhivaates STR_CONFIG_SETTING_AUTOSCROLL_EVERY_VIEWPORT :Igas vaates STR_CONFIG_SETTING_BRIBE :Altkäemaksud kohalikele omavalitsustele: {STRING} -STR_CONFIG_SETTING_BRIBE_HELPTEXT :Võimaldab linna omavalitsusele altkäemaksu pakkuda. Vahele jäämise korral võetakse ettevõttelt kuueks kuuks ära õigus tegutseda linnas +STR_CONFIG_SETTING_BRIBE_HELPTEXT :Võimaldab asulate omavalitsustele altkäemaksu pakkuda. Tabamise korral võetakse ettevõttelt kuueks kuuks ära õigus asulas tegutseda STR_CONFIG_SETTING_ALLOW_EXCLUSIVE :Ainuveoõiguste ostmine: {STRING} -STR_CONFIG_SETTING_ALLOW_EXCLUSIVE_HELPTEXT :Kui ettevõte ostab linnalt kaubaveo ainuõiguse, siis teised ettevõtted ei saa üks aasta uusi kaupu ega reisijaid +STR_CONFIG_SETTING_ALLOW_EXCLUSIVE_HELPTEXT :Kui ettevõte ostab asulalt kaubaveo ainuõiguse, siis teised ettevõtted ei saa üks aasta uusi kaupu ega reisijaid STR_CONFIG_SETTING_ALLOW_FUND_BUILDINGS :Luba ehitiste rahastamine: {STRING} -STR_CONFIG_SETTING_ALLOW_FUND_BUILDINGS_HELPTEXT :Võimaldab ettevõttel linna kinnisvaraehitust rahastada +STR_CONFIG_SETTING_ALLOW_FUND_BUILDINGS_HELPTEXT :Võimaldab ettevõttel asulate kinnisvaraehitust rahastada STR_CONFIG_SETTING_ALLOW_FUND_ROAD :Kohaliku omavalitsuse teede rekonstrueerimise rahastamine: {STRING} -STR_CONFIG_SETTING_ALLOW_FUND_ROAD_HELPTEXT :Võimaldab ettevõtetel linna tee-ehitustöid rahastada, šaboteerides sellega autode kaubavedu +STR_CONFIG_SETTING_ALLOW_FUND_ROAD_HELPTEXT :Võimaldab ettevõtetel asulate tee-ehitustöid rahastada, šaboteerides sellega autode kaubavedu STR_CONFIG_SETTING_ALLOW_GIVE_MONEY :Raha saatmine teistele ettevõtetele: {STRING} STR_CONFIG_SETTING_ALLOW_GIVE_MONEY_HELPTEXT :Luba mitmikmängus kanda raha erinevate ettevõtete vahel STR_CONFIG_SETTING_FREIGHT_TRAINS :Raskete rongide simuleerimiseks kasutatav raskustegur: {STRING} @@ -1333,8 +1333,8 @@ STR_CONFIG_SETTING_PLANE_CRASHES_HELPTEXT :Määrab lennu STR_CONFIG_SETTING_PLANE_CRASHES_NONE :Ei STR_CONFIG_SETTING_PLANE_CRASHES_REDUCED :Vähem STR_CONFIG_SETTING_PLANE_CRASHES_NORMAL :Keskmiselt -STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD :Läbisõidupeatused linnateedel: {STRING} -STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD_HELPTEXT :Lubab linnale kuuluvatel teedel ehitada läbisõidupeatuseid +STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD :Läbisõidupeatused asulate kuuluvatel teedel: {STRING} +STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD_HELPTEXT :Lubab asulatele kuuluvatele teedele ehitada läbisõidupeatuseid STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD :Läbisõidupeatused konkurentide teedel: {STRING} STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD_HELPTEXT :Lubab teisele ettevõttele kuulaval teel ehitada läbisõidupeatuseid STR_CONFIG_SETTING_DYNAMIC_ENGINES_EXISTING_VEHICLES :{WHITE}Sõidukite olemasolul ei saa seda seadet muuta @@ -1373,8 +1373,8 @@ STR_CONFIG_SETTING_HOVER_DELAY :Vihjeid näidat STR_CONFIG_SETTING_HOVER_DELAY_HELPTEXT :Viivitus, kui kaua peab hiirega liidese osale osutama, enne kui selle kohta vihjet näidatakse. On ka võimalik vihjeid parema hiireklõpsuga näidata STR_CONFIG_SETTING_HOVER_DELAY_VALUE :{COMMA} sekundilist osutamist STR_CONFIG_SETTING_HOVER_DELAY_DISABLED :Paremklõpsu -STR_CONFIG_SETTING_POPULATION_IN_LABEL :Linna rahvaarv nimesildil: {STRING} -STR_CONFIG_SETTING_POPULATION_IN_LABEL_HELPTEXT :Näitab linna nimesildil rahvaarvu +STR_CONFIG_SETTING_POPULATION_IN_LABEL :Asula rahvaarv nimesildil: {STRING} +STR_CONFIG_SETTING_POPULATION_IN_LABEL_HELPTEXT :Näitab asula nimesildil rahvaarvu STR_CONFIG_SETTING_GRAPH_LINE_THICKNESS :Graafikujoone laius: {STRING} STR_CONFIG_SETTING_GRAPH_LINE_THICKNESS_HELPTEXT :Graafikutes kasutatava joone laius. Peenem joon on täpsem, laiem joon on paremini näha ja värvid on selgemini eristatavad STR_CONFIG_SETTING_SHOW_NEWGRF_NAME :Näita sõidukiehitusaknas NewGRF-i nime: {STRING} @@ -1393,7 +1393,7 @@ STR_CONFIG_SETTING_INDUSTRY_DENSITY_HELPTEXT :Määrab, kui p STR_CONFIG_SETTING_OIL_REF_EDGE_DISTANCE :Maksimaalne kaugus kaardi servast naftatöötlustehase jaoks: {STRING} STR_CONFIG_SETTING_OIL_REF_EDGE_DISTANCE_HELPTEXT :Naftatöötlemistehased ehitatakse ainult kaardi piirile, see on rannikul saartega mängitaval kaardil. STR_CONFIG_SETTING_SNOWLINE_HEIGHT :Lumepiiri kõrgus: {STRING} -STR_CONFIG_SETTING_SNOWLINE_HEIGHT_HELPTEXT :Määra, millisel kõrgusel algab sub-arktiline maastik. Lumi mõjutab ka tööstuste loomist ja linna kasvamise tingimusi +STR_CONFIG_SETTING_SNOWLINE_HEIGHT_HELPTEXT :Määra, millisel kõrgusel algab sub-arktiline maastik. Lumi mõjutab ka tööstuste loomist ja asulate kasvamise nõudeid STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN :Maastiku järskus (ainult TerraGenesis) : {STRING} STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_HELPTEXT :(TerraGenesis only) Vali mägede tihedus: Laugetel maastikel on vähem, laiema ulatusega mägesid. Mägistel maastikel on palju mägesid, mis võivad näha välja samasugused STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_VERY_SMOOTH :Väga lauge @@ -1525,7 +1525,7 @@ STR_CONFIG_SETTING_SOUND_DISASTER_HELPTEXT :Õnnetustes ja STR_CONFIG_SETTING_SOUND_VEHICLE :Sõidukitel: {STRING} STR_CONFIG_SETTING_SOUND_VEHICLE_HELPTEXT :Kostuvad sõidukite helid STR_CONFIG_SETTING_SOUND_AMBIENT :Taustahelid: {STRING} -STR_CONFIG_SETTING_SOUND_AMBIENT_HELPTEXT :Maastikult, tööstustest ja linnadest kostuvad taustahelid +STR_CONFIG_SETTING_SOUND_AMBIENT_HELPTEXT :Maastikult, tööstustest ja asulatest kostuvad taustahelid STR_CONFIG_SETTING_DISABLE_UNSUITABLE_BUILDING :Sobivate sõidukite puudumisel on taristu ehitamine keelatud: {STRING} STR_CONFIG_SETTING_DISABLE_UNSUITABLE_BUILDING_HELPTEXT :Sisse lülitamisel on taristu ehitamine võimalik ainult juhul, kui sellega seonduvad sõidukid on juba saadaval. Väldib mõttetut raha ja aja kulu, kui ehitatakse taristu, mida ei saa kasutada @@ -1653,26 +1653,26 @@ STR_CONFIG_SETTING_CYCLE_SIGNAL_NORMAL :Ainult tavaline STR_CONFIG_SETTING_CYCLE_SIGNAL_PBS :Ainult täiustatud STR_CONFIG_SETTING_CYCLE_SIGNAL_ALL :Kõik -STR_CONFIG_SETTING_TOWN_LAYOUT :Uute linnade tänavaplaneering: {STRING} -STR_CONFIG_SETTING_TOWN_LAYOUT_HELPTEXT :Teedevõrgu paigutus linnades +STR_CONFIG_SETTING_TOWN_LAYOUT :Uute asulate teeplaneering: {STRING} +STR_CONFIG_SETTING_TOWN_LAYOUT_HELPTEXT :Teedevõrgu paigutus asulates STR_CONFIG_SETTING_TOWN_LAYOUT_DEFAULT :Esialgne STR_CONFIG_SETTING_TOWN_LAYOUT_BETTER_ROADS :Paremad teed STR_CONFIG_SETTING_TOWN_LAYOUT_2X2_GRID :2x2 võrgustik STR_CONFIG_SETTING_TOWN_LAYOUT_3X3_GRID :3x3 võrgustik STR_CONFIG_SETTING_TOWN_LAYOUT_RANDOM :Suvaline -STR_CONFIG_SETTING_ALLOW_TOWN_ROADS :Linnapoolne teedeehitus: {STRING} -STR_CONFIG_SETTING_ALLOW_TOWN_ROADS_HELPTEXT :Luba linnadel ehitada teid, et soodustada kasvu. Lülita välja, et keelata linnadel rajada teid -STR_CONFIG_SETTING_ALLOW_TOWN_LEVEL_CROSSINGS :Linnadel on lubatud ehitada ülesõidukohti: {STRING} -STR_CONFIG_SETTING_ALLOW_TOWN_LEVEL_CROSSINGS_HELPTEXT :Selle seade sisse lülitamine lubab linnadel ehitada ülesõidukohti -STR_CONFIG_SETTING_NOISE_LEVEL :Lennujaamade mürapiirangud linnades: {STRING} -STR_CONFIG_SETTING_NOISE_LEVEL_HELPTEXT :Kui seade ei ole sisse lülitatud, siis igasse linna on lubatud ehitada kuni kaks lennuvälja. Kui see seade on lubatud, siis lennuväljade arvu ülempiiri määrab linna müratundlikkus, mis sõltub rahvaarvust ja lennuvälja suurusest ja kaugusest -STR_CONFIG_SETTING_TOWN_FOUNDING :Linnade rajamine keset mängu: {STRING} -STR_CONFIG_SETTING_TOWN_FOUNDING_HELPTEXT :Võimaldab ettevõttel uusi linnu rajada +STR_CONFIG_SETTING_ALLOW_TOWN_ROADS :Asulatel on lubatud teederajamine: {STRING} +STR_CONFIG_SETTING_ALLOW_TOWN_ROADS_HELPTEXT :Luba asulatele teederajamine, et soodustada kasvu. Lülita välja, et keelata asulatele teederajamine +STR_CONFIG_SETTING_ALLOW_TOWN_LEVEL_CROSSINGS :Asulatel on lubatud ehitada ülesõidukohti: {STRING} +STR_CONFIG_SETTING_ALLOW_TOWN_LEVEL_CROSSINGS_HELPTEXT :Selle seade sisse lülitamine lubab asulatel ehitada ülesõidukohti +STR_CONFIG_SETTING_NOISE_LEVEL :Lennujaamade mürapiirangud asulates: {STRING} +STR_CONFIG_SETTING_NOISE_LEVEL_HELPTEXT :Kui seade ei ole sisse lülitatud, siis igasse asulasse on lubatud ehitada kuni kaks lennuvälja. Kui see seade on lubatud, siis lennuväljade arvu ülempiiri määrab asustuse müratundlikkus, mis sõltub rahvaarvust ja lennuvälja suurusest ja kaugusest +STR_CONFIG_SETTING_TOWN_FOUNDING :Asulate rajamine keset mängu: {STRING} +STR_CONFIG_SETTING_TOWN_FOUNDING_HELPTEXT :Võimaldab ettevõttel uusi asulaid rajada STR_CONFIG_SETTING_TOWN_FOUNDING_FORBIDDEN :Keelatud STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED :Lubatud -STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED_CUSTOM_LAYOUT :Lubatud, koos linnaskeemi valimisega -STR_CONFIG_SETTING_TOWN_CARGOGENMODE :Kauba tekkimine linnades: {STRING} -STR_CONFIG_SETTING_TOWN_CARGOGENMODE_HELPTEXT :Kui palju kaupa toodavad majad linnades, võrreldes linnaelanike arvuga.{}Ruutfunktsioon: kaks korda suurem linn tekitab neli korda rohkem reisijaid.{}Lineaarne: kaks korda suurem linn tekitab kaks korda rohkem reisijaid. +STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED_CUSTOM_LAYOUT :Lubatud, koos asulaskeemi valimisega +STR_CONFIG_SETTING_TOWN_CARGOGENMODE :Kauba tekkimine asulates: {STRING} +STR_CONFIG_SETTING_TOWN_CARGOGENMODE_HELPTEXT :Kui palju kaupa toodavad majad asulates, võrreldes elanike arvuga.{}Ruutfunktsioon: kaks korda suurem asula tekitab neli korda rohkem reisijaid.{}Lineaarne: kaks korda suurem asula tekitab kaks korda rohkem reisijaid. STR_CONFIG_SETTING_TOWN_CARGOGENMODE_ORIGINAL :Ruutfunktsioon (algupärane) STR_CONFIG_SETTING_TOWN_CARGOGENMODE_BITCOUNT :Lineaarne @@ -1705,18 +1705,18 @@ STR_CONFIG_SETTING_ZOOM_LVL_NORMAL :Tavaline STR_CONFIG_SETTING_ZOOM_LVL_OUT_2X :2x STR_CONFIG_SETTING_ZOOM_LVL_OUT_4X :4x STR_CONFIG_SETTING_ZOOM_LVL_OUT_8X :8x -STR_CONFIG_SETTING_TOWN_GROWTH :Linna kasvutempo: {STRING} -STR_CONFIG_SETTING_TOWN_GROWTH_HELPTEXT :Linna kasvamise kiirus +STR_CONFIG_SETTING_TOWN_GROWTH :Asulate kasvutempo: {STRING} +STR_CONFIG_SETTING_TOWN_GROWTH_HELPTEXT :Asulate kasvutempo STR_CONFIG_SETTING_TOWN_GROWTH_NONE :Pole STR_CONFIG_SETTING_TOWN_GROWTH_SLOW :Aeglane STR_CONFIG_SETTING_TOWN_GROWTH_NORMAL :Normaalne STR_CONFIG_SETTING_TOWN_GROWTH_FAST :Kiire STR_CONFIG_SETTING_TOWN_GROWTH_VERY_FAST :Väga Kiire -STR_CONFIG_SETTING_LARGER_TOWNS :Proportsioon asulatest millest kasvavad linnad: {STRING} +STR_CONFIG_SETTING_LARGER_TOWNS :Linnadeks kasvavate asulate osakaal: {STRING} STR_CONFIG_SETTING_LARGER_TOWNS_HELPTEXT :Asulate arv mis kasvavad tulevikus linnadeks, elik asulate arv mis on alguses suuremad ja kasvavad kiiremini kui ülejäänud asulad STR_CONFIG_SETTING_LARGER_TOWNS_VALUE :1 iga {COMMA} kohta STR_CONFIG_SETTING_LARGER_TOWNS_DISABLED :Mitte ühtegi -STR_CONFIG_SETTING_CITY_SIZE_MULTIPLIER :Algne linnade suurustegur: {STRING} +STR_CONFIG_SETTING_CITY_SIZE_MULTIPLIER :Algne linnade suuruskordaja: {STRING} STR_CONFIG_SETTING_CITY_SIZE_MULTIPLIER_HELPTEXT :Keskmine linna suurus võrreldes tavalise asulaga mängu alguses STR_CONFIG_SETTING_LINKGRAPH_INTERVAL :Jaotusgraafikuid uuendatakse igal {STRING}. päeval @@ -1797,8 +1797,8 @@ STR_CONFIG_SETTING_LIMITATIONS :{ORANGE}Piirang STR_CONFIG_SETTING_ACCIDENTS :{ORANGE}Katastroofid / Õnnetused STR_CONFIG_SETTING_GENWORLD :{ORANGE}Maailma tekitamine STR_CONFIG_SETTING_ENVIRONMENT :{ORANGE}Keskkond -STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES :{ORANGE}Linnavõimud -STR_CONFIG_SETTING_ENVIRONMENT_TOWNS :{ORANGE}Linnad +STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES :{ORANGE}Omavalitsus +STR_CONFIG_SETTING_ENVIRONMENT_TOWNS :{ORANGE}Asulad STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES :{ORANGE}Tööstused STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST :{ORANGE}Kaubajaotus STR_CONFIG_SETTING_AI :{ORANGE}Konkurendid @@ -2629,31 +2629,31 @@ STR_QUERY_RESET_LANDSCAPE_CAPTION :{WHITE}Nulli ma STR_RESET_LANDSCAPE_CONFIRMATION_TEXT :{WHITE}Kas oled kindel, et tahad lammutada kõik mängija omandid? # Town generation window (SE) -STR_FOUND_TOWN_CAPTION :{WHITE}Linnade tekitamine -STR_FOUND_TOWN_NEW_TOWN_BUTTON :{BLACK}Uus linn -STR_FOUND_TOWN_NEW_TOWN_TOOLTIP :{BLACK}Ehita uus linn. Shift+klõpsuga kuvatakse eeldatav maksumus -STR_FOUND_TOWN_RANDOM_TOWN_BUTTON :{BLACK}Suvaline linn -STR_FOUND_TOWN_RANDOM_TOWN_TOOLTIP :{BLACK}Ehita linn suvalisse kohta -STR_FOUND_TOWN_MANY_RANDOM_TOWNS :{BLACK}Palju suvalisi linnu -STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP :{BLACK}Kaardi katmine suvaliselt asetatud linnadega - -STR_FOUND_TOWN_NAME_TITLE :{YELLOW}Linnanimi: -STR_FOUND_TOWN_NAME_EDITOR_TITLE :{BLACK}Sisesta linnanimi -STR_FOUND_TOWN_NAME_EDITOR_HELP :{BLACK}Klõpsa linnale nime andmiseks +STR_FOUND_TOWN_CAPTION :{WHITE}Asulate tekitamine +STR_FOUND_TOWN_NEW_TOWN_BUTTON :{BLACK}Uus asula +STR_FOUND_TOWN_NEW_TOWN_TOOLTIP :{BLACK}Raja uus asula. Shift+klõpsuga kuvatakse eeldatav maksumus +STR_FOUND_TOWN_RANDOM_TOWN_BUTTON :{BLACK}Suvaline asula +STR_FOUND_TOWN_RANDOM_TOWN_TOOLTIP :{BLACK}Raja asula suvalisse kohta +STR_FOUND_TOWN_MANY_RANDOM_TOWNS :{BLACK}Palju suvalisi asulaid +STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP :{BLACK}Kaardi katmine suvaliselt paigutatud asulatega + +STR_FOUND_TOWN_NAME_TITLE :{YELLOW}Asula nimi: +STR_FOUND_TOWN_NAME_EDITOR_TITLE :{BLACK}Sisesta asula nimi +STR_FOUND_TOWN_NAME_EDITOR_HELP :{BLACK}Klõpsa asulale nime andmiseks STR_FOUND_TOWN_NAME_RANDOM_BUTTON :{BLACK}Suvaline nimi STR_FOUND_TOWN_NAME_RANDOM_TOOLTIP :{BLACK}Loo suvaline nimi -STR_FOUND_TOWN_INITIAL_SIZE_TITLE :{YELLOW}Linna suurus: +STR_FOUND_TOWN_INITIAL_SIZE_TITLE :{YELLOW}Asula suurus: STR_FOUND_TOWN_INITIAL_SIZE_SMALL_BUTTON :{BLACK}Väike STR_FOUND_TOWN_INITIAL_SIZE_MEDIUM_BUTTON :{BLACK}Keskmine STR_FOUND_TOWN_INITIAL_SIZE_LARGE_BUTTON :{BLACK}Suur STR_FOUND_TOWN_SIZE_RANDOM :{BLACK}Suvaline -STR_FOUND_TOWN_INITIAL_SIZE_TOOLTIP :{BLACK}Vali linna suurus +STR_FOUND_TOWN_INITIAL_SIZE_TOOLTIP :{BLACK}Vali asula suurus STR_FOUND_TOWN_CITY :{BLACK}Linn -STR_FOUND_TOWN_CITY_TOOLTIP :{BLACK}Suuremad linnad kasvavad kiiremini kui väikesed{}Sõltuvalt seadetest on need ka alguses suuremad +STR_FOUND_TOWN_CITY_TOOLTIP :{BLACK}Linnad kasvavad kiiremini, kui tavalised asulad{}Sõltuvalt seadetest on need ka alguses suuremad -STR_FOUND_TOWN_ROAD_LAYOUT :{YELLOW}Linna teeplaneering -STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT :{BLACK}Vali linnas kasutatav teeplaneering +STR_FOUND_TOWN_ROAD_LAYOUT :{YELLOW}Asula teeplaneering +STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT :{BLACK}Vali selle asula teeplaneering STR_FOUND_TOWN_SELECT_LAYOUT_ORIGINAL :{BLACK}Algne STR_FOUND_TOWN_SELECT_LAYOUT_BETTER_ROADS :{BLACK}Paremad teed STR_FOUND_TOWN_SELECT_LAYOUT_2X2_GRID :{BLACK}2x2 ruudustik @@ -2907,7 +2907,7 @@ STR_MAPGEN_WORLD_GENERATION_CAPTION :{WHITE}Maailma STR_MAPGEN_MAPSIZE :{BLACK}Kaardi mõõtmed: STR_MAPGEN_MAPSIZE_TOOLTIP :{BLACK}Määrab maakaardi mõõtmed ruutudes. Ruute, mida saab kasutada, on pisut vähem STR_MAPGEN_BY :{BLACK}* -STR_MAPGEN_NUMBER_OF_TOWNS :{BLACK}Linnade sagedus: +STR_MAPGEN_NUMBER_OF_TOWNS :{BLACK}Asustusi: STR_MAPGEN_DATE :{BLACK}Algus: STR_MAPGEN_NUMBER_OF_INDUSTRIES :{BLACK}Tööstuste sagedus: STR_MAPGEN_MAX_HEIGHTLEVEL :{BLACK}Maksimaalne kaardi kõrgus: @@ -3167,11 +3167,11 @@ STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP :{BLACK}Mine eel STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Sisesta sildile nimi # Town directory window -STR_TOWN_DIRECTORY_CAPTION :{WHITE}Linnad +STR_TOWN_DIRECTORY_CAPTION :{WHITE}Asustused STR_TOWN_DIRECTORY_NONE :{ORANGE}- Puudub - STR_TOWN_DIRECTORY_TOWN :{ORANGE}{TOWN}{BLACK} ({COMMA}) STR_TOWN_DIRECTORY_CITY :{ORANGE}{TOWN}{YELLOW} (Linn){BLACK} ({COMMA}) -STR_TOWN_DIRECTORY_LIST_TOOLTIP :{BLACK}Linnanimed - vajuta linnanimele, et viia vaade linnale. Ctrl+klõps avab uue vaate linna asukohast +STR_TOWN_DIRECTORY_LIST_TOOLTIP :{BLACK}Astuste nimed - vajuta nimele, et viia vaade asustusele. Ctrl+klõps avab uue vaate linna asukohas STR_TOWN_POPULATION :{BLACK}Maailma rahvastik: {COMMA} # Town view window @@ -3179,25 +3179,25 @@ STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN} STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Linn) STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Rahvaarv: {ORANGE}{COMMA}{BLACK} Ehitisi: {ORANGE}{COMMA} STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} eelmisel kuul: {ORANGE}{COMMA}{BLACK} Kuni: {ORANGE}{COMMA} -STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Veoseid linna kasvamiseks: +STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Asula kasvuks nõutud veosed: STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} vajalik STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} on talvel vajalik STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_DELIVERED_GENERAL :{ORANGE}{STRING}{GREEN} äraveetud STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED :{ORANGE}{CARGO_TINY} / {CARGO_LONG}{RED} (ikka veel vaja) STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_DELIVERED :{ORANGE}{CARGO_TINY} / {CARGO_LONG}{GREEN} (äraveetud) -STR_TOWN_VIEW_TOWN_GROWS_EVERY :{BLACK}Linn kasvab {ORANGE}{COMMA}{BLACK} päevaga -STR_TOWN_VIEW_TOWN_GROWS_EVERY_FUNDED :{BLACK}Linn kasvab iga {ORANGE}{COMMA}{BLACK} päeva tagant (rahastatud) -STR_TOWN_VIEW_TOWN_GROW_STOPPED :{BLACK}Linn {RED}ei{BLACK} kasva -STR_TOWN_VIEW_NOISE_IN_TOWN :{BLACK}Mürapiirang linnas: {ORANGE}{COMMA}{BLACK} suurim: {ORANGE}{COMMA} -STR_TOWN_VIEW_CENTER_TOOLTIP :{BLACK}Vaate viimine linnale +STR_TOWN_VIEW_TOWN_GROWS_EVERY :{BLACK}Asustus kasvab iga {ORANGE}{COMMA}{NBSP}päev{P "" a} +STR_TOWN_VIEW_TOWN_GROWS_EVERY_FUNDED :{BLACK}Asustus kasvab iga {ORANGE}{COMMA}{BLACK}{NBSP}päev{P "" a} (rahastatud) +STR_TOWN_VIEW_TOWN_GROW_STOPPED :{BLACK}Asustus {RED}ei{BLACK} kasva +STR_TOWN_VIEW_NOISE_IN_TOWN :{BLACK}Mürapiirang asustuses: {ORANGE}{COMMA}{BLACK} suurim: {ORANGE}{COMMA} +STR_TOWN_VIEW_CENTER_TOOLTIP :{BLACK}Keskenda vaade asustusele. Ctrl+klõps avab asustuse kohal uue vaate STR_TOWN_VIEW_LOCAL_AUTHORITY_BUTTON :{BLACK}Omavalitsus STR_TOWN_VIEW_LOCAL_AUTHORITY_TOOLTIP :{BLACK}Näita teavet kohaliku omavalitsuse kohta STR_TOWN_VIEW_RENAME_TOOLTIP :{BLACK}Muuda linna nime STR_TOWN_VIEW_EXPAND_BUTTON :{BLACK}Laienda -STR_TOWN_VIEW_EXPAND_TOOLTIP :{BLACK}Linna suuruse suurendamine +STR_TOWN_VIEW_EXPAND_TOOLTIP :{BLACK}Asustuse suurendamine STR_TOWN_VIEW_DELETE_BUTTON :{BLACK}Kustuta -STR_TOWN_VIEW_DELETE_TOOLTIP :{BLACK}Hävita see linn täielikult +STR_TOWN_VIEW_DELETE_TOOLTIP :{BLACK}Hävita see asustus täielikult STR_TOWN_VIEW_RENAME_TOWN_BUTTON :Ümbernimeta linn @@ -3208,7 +3208,7 @@ STR_LOCAL_AUTHORITY_ZONE_TOOLTIP :{BLACK}Näita k STR_LOCAL_AUTHORITY_COMPANY_RATINGS :{BLACK}Ettevõtete hinnangud: STR_LOCAL_AUTHORITY_COMPANY_RATING :{YELLOW}{COMPANY} {COMPANY_NUM}: {ORANGE}{STRING} STR_LOCAL_AUTHORITY_ACTIONS_TITLE :{BLACK}Pakutavad toimingud: -STR_LOCAL_AUTHORITY_ACTIONS_TOOLTIP :{BLACK}Nimekiri asjadest, mida saab siin linnas teha - klõpsa esemel, et saada rohkem andmeid +STR_LOCAL_AUTHORITY_ACTIONS_TOOLTIP :{BLACK}Nimekiri asjadest, mida saab selles asulas teha - täpsustused esemele klõpsatel STR_LOCAL_AUTHORITY_DO_IT_BUTTON :{BLACK}Soorita toiming STR_LOCAL_AUTHORITY_DO_IT_TOOLTIP :{BLACK}Soorita ülalpool valitud toiming @@ -3221,14 +3221,14 @@ STR_LOCAL_AUTHORITY_ACTION_NEW_BUILDINGS :Rahasta uute ho STR_LOCAL_AUTHORITY_ACTION_EXCLUSIVE_TRANSPORT :Osta monopoolsed veoõigused STR_LOCAL_AUTHORITY_ACTION_BRIBE :Paku kohalikule omavalitsusele altkäemaksu -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_SMALL_ADVERTISING :{YELLOW} Pisikese kohaliku reklaamikampaania korraldamine meelitab rohkem reisijaid ja kaupu sinu veoteenuseid kasutama.{} Hind: {CURRENCY_LONG} -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_MEDIUM_ADVERTISING :{YELLOW} Keskmise reklaamikampaania korraldamine, et meelitada rohkem reisijaid ja kaupu sinu teenuste juurede.{} Hind: {CURRENCY_LONG} -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_LARGE_ADVERTISING :{YELLOW} Suure reklaamikampaania korraldamine, et meelitada rohkem reisijaid ja kaupu sinu teenuste juurede.{} Hind: {CURRENCY_LONG} -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_ROAD_RECONSTRUCTION :{YELLOW} Teehoiutööde rahastamine. Põhjustab linnatänavatel tõsiseid liiklusprobleeme kuni 6 kuuks.{} Hind: {CURRENCY_LONG} -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_STATUE_OF_COMPANY :{YELLOW} Oma ettevõtte auks kuju ehitamine.{} Hind: {CURRENCY_LONG} -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_NEW_BUILDINGS :{YELLOW} Uute ärihoonete ehitamise rahastamine selles linnas.{} Hind: {CURRENCY_LONG} -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_EXCLUSIVE_TRANSPORT :{YELLOW} Üheks aastaks monopoolsete veoõiguste ostmine selles linnas. Linna omavalitsus lubab reisijate ja kauba veoks kasutada ainult sinu jaamasid.{} Hind: {CURRENCY_LONG} -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_BRIBE :{YELLOW} Linna omavalitusele ettevõtte hinnangu tõstmise eesmärgil altkäemaksu pakkumine. Vahele jäädes tuleb trahvi maksta.{} Hind: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_SMALL_ADVERTISING :{YELLOW} Pisikese kohaliku reklaamikampaania korraldamine meelitab rohkem reisijaid ja kaupu sinu veoteenuseid kasutama.{} Maksumus: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_MEDIUM_ADVERTISING :{YELLOW} Keskmise reklaamikampaania korraldamine, et meelitada rohkem reisijaid ja kaupu sinu teenuste juurede.{} Maksumus: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_LARGE_ADVERTISING :{YELLOW} Suure reklaamikampaania korraldamine, et meelitada rohkem reisijaid ja kaupu sinu teenuste juurede.{} Maksumus: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_ROAD_RECONSTRUCTION :{YELLOW} Linna teedevõrgu ümberehituse rahastamine. Põhjustab liikluses tõsiseid häireid kuni 6 kuuks.{} Maksumus: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_STATUE_OF_COMPANY :{YELLOW} Oma ettevõtte auks kuju ehitamine.{} Maksumus: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_NEW_BUILDINGS :{YELLOW} Uute ärihoonete ehitamise rahastamine selles asulas.{} Maksumus: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_EXCLUSIVE_TRANSPORT :{YELLOW} Üheks aastaks monopoolsete veoõiguste ostmine selles asulas. Asula omavalitsus lubab reisijate ja kauba veoks kasutada ainult sinu jaamasid.{} Maksumus: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_BRIBE :{YELLOW} Kohalikule omavalitusele ettevõtte hinnangu tõstmise eesmärgil altkäemaksu pakkumine. Vahele jäädes tuleb trahvi maksta.{} Maksumus: {CURRENCY_LONG} # Goal window STR_GOALS_CAPTION :{WHITE}{COMPANY} eesmärgid @@ -3241,7 +3241,7 @@ STR_GOALS_SPECTATOR_NONE :{ORANGE}- Kehte STR_GOALS_PROGRESS :{ORANGE}{STRING} STR_GOALS_PROGRESS_COMPLETE :{GREEN}{STRING} STR_GOALS_COMPANY_TITLE :{BLACK}Ettevõtte eesmärgid: -STR_GOALS_TOOLTIP_CLICK_ON_SERVICE_TO_CENTER :{BLACK}Klõpsa eesmärgil, et liigutada vaateaken tööstuse/linna/ruudu asukohale. Ctrl+klõps avab uue vaateakna tööstuse/linna/ruudu asukohas +STR_GOALS_TOOLTIP_CLICK_ON_SERVICE_TO_CENTER :{BLACK}Klõpsa eesmärgil, et liigutada vaateaken tööstuse/asula/ruudu asukohale. Ctrl+klõps avab uue vaateakna tööstuse/asula/ruudu asukohas # Goal question window STR_GOAL_QUESTION_CAPTION_QUESTION :{BLACK}Küsimus @@ -3277,7 +3277,7 @@ STR_SUBSIDIES_OFFERED_FROM_TO :{ORANGE}{STRING STR_SUBSIDIES_NONE :{ORANGE}Mitte ühtegi STR_SUBSIDIES_SUBSIDISED_TITLE :{BLACK}Juba toetatavad veoteenused: STR_SUBSIDIES_SUBSIDISED_FROM_TO :{ORANGE}{STRING} kohast {STRING} kohta {STRING}{YELLOW} ({COMPANY}{YELLOW}, kuni {DATE_SHORT}) -STR_SUBSIDIES_TOOLTIP_CLICK_ON_SERVICE_TO_CENTER :{BLACK}Vajuta pakkumisele, et näha linna või tööstuse asukohta. Ctrl+klõps avab uue vaate ettevõtte/linna asukohast +STR_SUBSIDIES_TOOLTIP_CLICK_ON_SERVICE_TO_CENTER :{BLACK}Vajuta pakkumisele, et keskendada vaade tööstusele/asustusele. Ctrl+klõps avab uue vaate ettevõtte/asustuse asukohas # Story book window STR_STORY_BOOK_CAPTION :{WHITE}«{COMPANY}» juturaamat @@ -3313,8 +3313,8 @@ STR_STATION_VIEW_ACCEPTS_BUTTON :{BLACK}Võtab v STR_STATION_VIEW_ACCEPTS_TOOLTIP :{BLACK}Näita vastuvõetavate veoste nimistut STR_STATION_VIEW_ACCEPTS_CARGO :{BLACK}Võtab vastu: {WHITE}{CARGO_LIST} -STR_STATION_VIEW_EXCLUSIVE_RIGHTS_SELF :{BLACK}Antud jaamal on eksklusiivsed transpordiõigused selles linnas. -STR_STATION_VIEW_EXCLUSIVE_RIGHTS_COMPANY :{YELLOW}{COMPANY}{BLACK} ostis eksklusiivsed transpordiõigused antud linnas. +STR_STATION_VIEW_EXCLUSIVE_RIGHTS_SELF :{BLACK}Sellel jaamal on asulas vedude ainuõigus. +STR_STATION_VIEW_EXCLUSIVE_RIGHTS_COMPANY :{YELLOW}{COMPANY}{BLACK} ostis vedude ainuõiguse selles asustuses. STR_STATION_VIEW_RATINGS_BUTTON :{BLACK}Hinnangud STR_STATION_VIEW_RATINGS_TOOLTIP :{BLACK}Näita jaamahinnangut @@ -3874,7 +3874,7 @@ STR_VEHICLE_STATUS_HEADING_FOR_STATION_VEL :{LTBLUE}Siht: { STR_VEHICLE_STATUS_NO_ORDERS_VEL :{LTBLUE}Sihitu, {VELOCITY} STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT_VEL :{LTBLUE}Suundub {WAYPOINT}, {VELOCITY} STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_VEL :{ORANGE}Suundub {DEPOT}, {VELOCITY} -STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_SERVICE_VEL :{LTBLUE}Teenus kohas {DEPOT}, {VELOCITY} +STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_SERVICE_VEL :{LTBLUE}Järelevaatus: {DEPOT}, {VELOCITY} # Vehicle stopped/started animations STR_VEHICLE_COMMAND_STOPPED_SMALL :{TINY_FONT}{RED}peatatud @@ -4336,8 +4336,8 @@ STR_GAME_SAVELOAD_NOT_AVAILABLE :Majandus->Linnad +STR_ERROR_TOWN_EXPAND_WARN_NO_ROADS :{WHITE}Asula ei ehita teid. Teedeehituse lubamiseks Põhjalik seadistus->Majandus->Asulad STR_ERROR_ROAD_WORKS_IN_PROGRESS :{WHITE}Teede ehitamine -STR_ERROR_TOWN_CAN_T_DELETE :{WHITE}Seda linna ei saa kustutada...{}Jaam või depoo viitab linnale või mõnd linna omanduses olevat tükki ei saa kustutada -STR_ERROR_STATUE_NO_SUITABLE_PLACE :{WHITE}... linnakeskuses ei leidu kujule sobivat kohta +STR_ERROR_TOWN_CAN_T_DELETE :{WHITE}Seda asulat ei saa kõrvaldada...{}Jaam või depoo viitab asulale, või asulale kuuluvat ruutu ei saa kõrvaldada +STR_ERROR_STATUE_NO_SUITABLE_PLACE :{WHITE}... asula keskuses ei leidu kujule sobivat kohta # Industry related errors STR_ERROR_TOO_MANY_INDUSTRIES :{WHITE}... liiga palju tööstuseid @@ -4439,13 +4439,13 @@ STR_ERROR_CAN_T_GENERATE_INDUSTRIES :{WHITE}Tööstu STR_ERROR_CAN_T_BUILD_HERE :{WHITE}{STRING} ei saa siia ehitada... STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY :{WHITE}Seda tüüpi tööstust ei saa siia ehitada... STR_ERROR_INDUSTRY_TOO_CLOSE :{WHITE}... liiga lähedal mõnele teisele tööstusele -STR_ERROR_MUST_FOUND_TOWN_FIRST :{WHITE}... enne pead linna ehitama -STR_ERROR_ONLY_ONE_ALLOWED_PER_TOWN :{WHITE}... iga linna kohta lubatud ainult üks -STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS_WITH_POPULATION_OF_1200 :{WHITE}... saab ainult ehitada linna, mille rahvastikuarv ületab 1200 +STR_ERROR_MUST_FOUND_TOWN_FIRST :{WHITE}... enne pead asula rajama +STR_ERROR_ONLY_ONE_ALLOWED_PER_TOWN :{WHITE}... iga asula kohta lubatud ainult üks +STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS_WITH_POPULATION_OF_1200 :{WHITE}... saab ainult ehitada asulatesse, mille elanikearv ületab 1200 STR_ERROR_CAN_ONLY_BE_BUILT_IN_RAINFOREST :{WHITE}... saab ehitada ainult vihmametsadesse STR_ERROR_CAN_ONLY_BE_BUILT_IN_DESERT :{WHITE}... saab ehitada ainult kõrbesse -STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS :{WHITE}... saab ainult linnadesse ehitada -STR_ERROR_CAN_ONLY_BE_BUILT_NEAR_TOWN_CENTER :{WHITE}... saab ainult linna keskpunkti lähedale ehitada +STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS :{WHITE}... saab ainult asulatesse ehitada (majade asemele) +STR_ERROR_CAN_ONLY_BE_BUILT_NEAR_TOWN_CENTER :{WHITE}... saab ainult asulekeskuste lähedale ehitada STR_ERROR_CAN_ONLY_BE_BUILT_IN_LOW_AREAS :{WHITE}... saab ainult madalatele aladele ehitada STR_ERROR_CAN_ONLY_BE_POSITIONED :{WHITE}... saab ainult ehitada kaardi serva STR_ERROR_FOREST_CAN_ONLY_BE_PLANTED :{WHITE}... metsa saab istutada ainult lumepiirist kõrgemale @@ -4473,7 +4473,7 @@ STR_ERROR_TOO_MANY_TRUCK_STOPS :{WHITE}Liiga pa STR_ERROR_TOO_CLOSE_TO_ANOTHER_DOCK :{WHITE}Liiga lähedal teisele dokile STR_ERROR_TOO_CLOSE_TO_ANOTHER_AIRPORT :{WHITE}Liiga lähedal teisele lennuväljale STR_ERROR_CAN_T_RENAME_STATION :{WHITE}Ei saa jaama ümbernimetada... -STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... see tee on omatud linna poolt +STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... see on asulale kuuluv tee STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... tee on vales suunas STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... nurgad ei saa läbisõidupeatustes olla STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... ristmikud ei saa olla läbisõidupeatustes @@ -4748,7 +4748,7 @@ STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_1 :Büroohoone STR_TOWN_BUILDING_NAME_SMALL_BLOCK_OF_FLATS_1 :Väike kortermaja STR_TOWN_BUILDING_NAME_CHURCH_1 :Kirik STR_TOWN_BUILDING_NAME_LARGE_OFFICE_BLOCK_1 :Suur büroohoone -STR_TOWN_BUILDING_NAME_TOWN_HOUSES_1 :Linna majad +STR_TOWN_BUILDING_NAME_TOWN_HOUSES_1 :Asustuse majad STR_TOWN_BUILDING_NAME_HOTEL_1 :Hotell STR_TOWN_BUILDING_NAME_STATUE_1 :Kuju STR_TOWN_BUILDING_NAME_FOUNTAIN_1 :Purskkaev From 144991990ed35c97d02e221240e8f4e48704faac Mon Sep 17 00:00:00 2001 From: glx22 Date: Tue, 2 Feb 2021 00:18:20 +0100 Subject: [PATCH 002/122] Fix: [CMake] Language files should depend on english.txt --- src/lang/CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lang/CMakeLists.txt b/src/lang/CMakeLists.txt index 75d06f530a..f3aedca4b4 100644 --- a/src/lang/CMakeLists.txt +++ b/src/lang/CMakeLists.txt @@ -1,4 +1,9 @@ +set(MASTER_LANG_FILE + ${CMAKE_CURRENT_SOURCE_DIR}/english.txt +) + set(LANG_SOURCE_FILES + ${MASTER_LANG_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/afrikaans.txt ${CMAKE_CURRENT_SOURCE_DIR}/arabic_egypt.txt ${CMAKE_CURRENT_SOURCE_DIR}/basque.txt @@ -10,7 +15,6 @@ set(LANG_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/czech.txt ${CMAKE_CURRENT_SOURCE_DIR}/danish.txt ${CMAKE_CURRENT_SOURCE_DIR}/dutch.txt - ${CMAKE_CURRENT_SOURCE_DIR}/english.txt ${CMAKE_CURRENT_SOURCE_DIR}/english_AU.txt ${CMAKE_CURRENT_SOURCE_DIR}/english_US.txt ${CMAKE_CURRENT_SOURCE_DIR}/esperanto.txt @@ -75,7 +79,7 @@ foreach(LANG_SOURCE_FILE IN LISTS LANG_SOURCE_FILES) -s ${CMAKE_CURRENT_SOURCE_DIR} -d ${LANG_BINARY_DIR} ${LANG_SOURCE_FILE} - DEPENDS strgen + DEPENDS strgen ${MASTER_LANG_FILE} MAIN_DEPENDENCY ${LANG_SOURCE_FILE} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Compiling language ${LANG_SOURCE_FILE_NAME_WE}" @@ -103,7 +107,7 @@ add_custom_command_timestamp(OUTPUT ${TABLE_BINARY_DIR}/strings.h COMMAND strgen -s ${CMAKE_CURRENT_SOURCE_DIR} -d ${TABLE_BINARY_DIR} - DEPENDS strgen ${LANG_SOURCE_FILES} + DEPENDS strgen ${MASTER_LANG_FILE} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating table/strings.h" ) From 5b3fe4ae2741ef014b1a41a21c146d56c7408d6e Mon Sep 17 00:00:00 2001 From: translators Date: Tue, 2 Feb 2021 18:17:17 +0000 Subject: [PATCH 003/122] Update: Translations from eints german: 4 changes by Wuzzy2 --- src/lang/german.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/german.txt b/src/lang/german.txt index 4cde7ed7c1..c030b91aed 100644 --- a/src/lang/german.txt +++ b/src/lang/german.txt @@ -3321,7 +3321,7 @@ STR_BUOY_VIEW_CHANGE_BUOY_NAME :{BLACK}Namen de STR_EDIT_WAYPOINT_NAME :{WHITE}Namen des Wegpunkts ändern # Finances window -STR_FINANCES_CAPTION :{WHITE}{COMPANY} Finanzen {BLACK}{COMPANY_NUM} +STR_FINANCES_CAPTION :{WHITE}{COMPANY} - Finanzen {BLACK}{COMPANY_NUM} STR_FINANCES_EXPENDITURE_INCOME_TITLE :{WHITE}Ausgaben / Einnahmen STR_FINANCES_YEAR :{WHITE}{NUM} STR_FINANCES_SECTION_CONSTRUCTION :{GOLD}Baukosten @@ -3512,8 +3512,8 @@ STR_GROUP_REMOVE_ALL_VEHICLES :Liste leeren STR_GROUP_RENAME_CAPTION :{BLACK}Gruppe umbenennen -STR_GROUP_PROFIT_THIS_YEAR :Gewinn in diesem Jahr: -STR_GROUP_PROFIT_LAST_YEAR :Gewinn im letzten Jahr: +STR_GROUP_PROFIT_THIS_YEAR :Gewinn dieses Jahr: +STR_GROUP_PROFIT_LAST_YEAR :Gewinn letztes Jahr: STR_GROUP_OCCUPANCY :Aktuelle Nutzung: STR_GROUP_OCCUPANCY_VALUE :{NUM}% @@ -4227,7 +4227,7 @@ STR_AI_SETTINGS_START_DELAY :Wartezeit in Ta # Textfile window STR_TEXTFILE_README_CAPTION :{WHITE}{STRING}-Liesmich von {STRING} -STR_TEXTFILE_CHANGELOG_CAPTION :{WHITE}{STRING} Änderungen von {STRING} +STR_TEXTFILE_CHANGELOG_CAPTION :{WHITE}{STRING}-Änderungen von {STRING} STR_TEXTFILE_LICENCE_CAPTION :{WHITE}{STRING}-Lizenz von {STRING} STR_TEXTFILE_WRAP_TEXT :{WHITE}Text umbrechen STR_TEXTFILE_WRAP_TEXT_TOOLTIP :{BLACK}Text des Fensters so umbrechen, dass es ohne Scrollen hineinpasst From a4035af3375825ae277f1363c5d814ee55b92d7c Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Fri, 5 Feb 2021 10:00:36 +0000 Subject: [PATCH 004/122] Codechange: Apply coding style (#8640) * Fix: Missing or needed spaces * Codechange: Remove space * Codechange: Remove space * Codechange: More missing spaces * Codechange: Missing spaces * Codechange: Remove space * Codechange: Remove space --- src/console_cmds.cpp | 8 ++++---- src/fontcache.cpp | 2 +- src/group_gui.cpp | 6 +++--- src/network/network_gui.cpp | 2 +- src/station_cmd.cpp | 6 +++--- src/town_cmd.cpp | 4 ++-- src/tree_cmd.cpp | 2 +- src/vehicle.cpp | 2 +- src/vehicle_gui.cpp | 1 - src/video/sdl2_v.cpp | 10 +++++----- src/viewport.cpp | 4 ++-- src/viewport_sprite_sorter_sse4.cpp | 4 ++-- 12 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 7dbd5658ce..905a41c3e7 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -1255,7 +1255,7 @@ DEF_CONSOLE_CMD(ConReloadAI) } /* First kill the company of the AI, then start a new one. This should start the current AI again */ - DoCommandP(0, CCA_DELETE | company_id << 16 | CRR_MANUAL << 24, 0,CMD_COMPANY_CTRL); + DoCommandP(0, CCA_DELETE | company_id << 16 | CRR_MANUAL << 24, 0, CMD_COMPANY_CTRL); DoCommandP(0, CCA_NEW_AI | company_id << 16, 0, CMD_COMPANY_CTRL); IConsolePrint(CC_DEFAULT, "AI reloaded."); @@ -2136,7 +2136,7 @@ static void ConDumpRoadTypes() grfs.emplace(grfid, grf); } IConsolePrintF(CC_DEFAULT, " %02u %s %c%c%c%c, Flags: %c%c%c%c%c, GRF: %08X, %s", - (uint) rt, + (uint)rt, RoadTypeIsTram(rt) ? "Tram" : "Road", rti->label >> 24, rti->label >> 16, rti->label >> 8, rti->label, HasBit(rti->flags, ROTF_CATENARY) ? 'c' : '-', @@ -2174,7 +2174,7 @@ static void ConDumpRailTypes() grfs.emplace(grfid, grf); } IConsolePrintF(CC_DEFAULT, " %02u %c%c%c%c, Flags: %c%c%c%c%c%c, GRF: %08X, %s", - (uint) rt, + (uint)rt, rti->label >> 24, rti->label >> 16, rti->label >> 8, rti->label, HasBit(rti->flags, RTF_CATENARY) ? 'c' : '-', HasBit(rti->flags, RTF_NO_LEVEL_CROSSING) ? 'l' : '-', @@ -2217,7 +2217,7 @@ static void ConDumpCargoTypes() grfs.emplace(grfid, grf); } IConsolePrintF(CC_DEFAULT, " %02u Bit: %2u, Label: %c%c%c%c, Callback mask: 0x%02X, Cargo class: %c%c%c%c%c%c%c%c%c%c%c, GRF: %08X, %s", - (uint) i, + (uint)i, spec->bitnum, spec->label >> 24, spec->label >> 16, spec->label >> 8, spec->label, spec->callback_mask, diff --git a/src/fontcache.cpp b/src/fontcache.cpp index ec336612a4..0b2764aabf 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -1008,7 +1008,7 @@ static void LoadWin32Font(FontSize fs) if (fontPath[0] != 0) { if (AddFontResourceEx(fontPath, FR_PRIVATE, 0) != 0) { /* Try a nice little undocumented function first for getting the internal font name. - * Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */ + * Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */ typedef BOOL(WINAPI * PFNGETFONTRESOURCEINFO)(LPCTSTR, LPDWORD, LPVOID, DWORD); #ifdef UNICODE static PFNGETFONTRESOURCEINFO GetFontResourceInfo = (PFNGETFONTRESOURCEINFO)GetProcAddress(GetModuleHandle(_T("Gdi32")), "GetFontResourceInfoW"); diff --git a/src/group_gui.cpp b/src/group_gui.cpp index 2ab2a5b51a..e711201a68 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -741,9 +741,9 @@ public: assert(vehgroup.NumVehicles() > 0); v = vehgroup.vehicles_begin[0]; /* - No VehicleClicked(v) support for now, because don't want - to enable any contextual actions except perhaps clicking/ctrl-clicking to clone orders. - */ + * No VehicleClicked(v) support for now, because don't want + * to enable any contextual actions except perhaps clicking/ctrl-clicking to clone orders. + */ break; } diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 42915c410e..eb7ff78b26 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -332,7 +332,7 @@ protected: if (r == 0) r = a->info.use_password - b->info.use_password; /* Finally sort on the number of clients of the server in reverse order. */ - return (r != 0) ? r < 0: !NGameClientSorter(a, b); + return (r != 0) ? r < 0 : !NGameClientSorter(a, b); } /** Sort the server list */ diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 7bd124f065..4077a7b8c6 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -4036,7 +4036,7 @@ uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, Sourc first_station = st; continue; } - if (used_stations.empty()) { + if (used_stations.empty()) { used_stations.reserve(2); used_stations.emplace_back(std::make_pair(first_station, 0)); } @@ -4081,9 +4081,9 @@ uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, Sourc moving += p.second; } - /* If there is some cargo left due to rounding issues distribute it among the best rated stations. */ + /* If there is some cargo left due to rounding issues distribute it among the best rated stations. */ if (amount > moving) { - std::sort(used_stations.begin(), used_stations.end(), [type] (const StationInfo &a, const StationInfo &b) { + std::sort(used_stations.begin(), used_stations.end(), [type](const StationInfo &a, const StationInfo &b) { return b.first->goods[type].rating < a.first->goods[type].rating; }); diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 1aadd5927b..c2d148a1a3 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -1117,7 +1117,7 @@ static bool GrowTownWithRoad(const Town *t, TileIndex tile, RoadBits rcmd) * @param road_dir The direction of the road * @return true if the road can be continued, else false */ -static bool CanRoadContinueIntoNextTile(const Town* t, const TileIndex tile, const DiagDirection road_dir) +static bool CanRoadContinueIntoNextTile(const Town *t, const TileIndex tile, const DiagDirection road_dir) { const int delta = TileOffsByDiagDir(road_dir); // +1 tile in the direction of the road TileIndex next_tile = tile + delta; // The tile beyond which must be connectable to the target tile @@ -1236,7 +1236,7 @@ static bool GrowTownWithBridge(const Town *t, const TileIndex tile, const DiagDi * @param tunnel_dir The valid direction in which to grow a tunnel * @return true if a tunnel has been built, else false */ -static bool GrowTownWithTunnel(const Town* t, const TileIndex tile, const DiagDirection tunnel_dir) +static bool GrowTownWithTunnel(const Town *t, const TileIndex tile, const DiagDirection tunnel_dir) { assert(tunnel_dir < DIAGDIR_END); diff --git a/src/tree_cmd.cpp b/src/tree_cmd.cpp index f4610b5699..dee79e1b72 100644 --- a/src/tree_cmd.cpp +++ b/src/tree_cmd.cpp @@ -762,7 +762,7 @@ static void TileLoop_Trees(TileIndex tile) break; case 6: // final stage of tree destruction - if (!CanPlantExtraTrees(tile)) { + if (!CanPlantExtraTrees(tile)) { /* if trees can't spread just plant a new one to prevent deforestation */ SetTreeGrowth(tile, 0); } else if (GetTreeCount(tile) > 1) { diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 21d02f1816..3bec190804 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1179,7 +1179,7 @@ void ViewportAddVehicles(DrawPixelInfo *dpi) t <= v->coord.bottom && r >= v->coord.left && b >= v->coord.top) DoDrawVehicle(v); - } + } v = v->hash_viewport_next; } diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 66db4f6e8c..d28849ebdc 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -1575,7 +1575,6 @@ void BaseVehicleListWindow::DrawVehicleListItems(VehicleID selected_vehicle, int } case GB_SHARED_ORDERS: - assert(vehgroup.NumVehicles() > 0); for (int i = 0; i < static_cast(vehgroup.NumVehicles()); ++i) { diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index aa7a23c728..66e6586fdc 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -524,7 +524,7 @@ static uint ConvertSdlKeycodeIntoMy(SDL_Keycode kc) } /* check scancode for BACKQUOTE key, because we want the key left - of "1", not anything else (on non-US keyboards) */ + * of "1", not anything else (on non-US keyboards) */ SDL_Scancode sc = SDL_GetScancodeFromKey(kc); if (sc == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE; @@ -542,10 +542,10 @@ int VideoDriver_SDL::PollEvent() #ifdef __EMSCRIPTEN__ if (_cursor_new_in_window) { /* The cursor just moved into the window; this means we don't - * know the absolutely position yet to move relative from. - * Before this time, SDL didn't know it either, and this is - * why we postpone it till now. Update the absolute position - * for this once, and work relative after. */ + * know the absolutely position yet to move relative from. + * Before this time, SDL didn't know it either, and this is + * why we postpone it till now. Update the absolute position + * for this once, and work relative after. */ _cursor.pos.x = ev.motion.x; _cursor.pos.y = ev.motion.y; _cursor.dirty = true; diff --git a/src/viewport.cpp b/src/viewport.cpp index 44ff37c741..822b0db9c2 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -1527,7 +1527,7 @@ static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv) sprite_list.sort(); - std::vector preceding; // Temporarily stores sprites that precede current and their position in the list + std::vector preceding; // Temporarily stores sprites that precede current and their position in the list auto preceding_prev = sprite_list.begin(); // Store iterator in case we need to delete a single preciding sprite auto out = psdv->begin(); // Iterator to output sorted sprites @@ -1606,7 +1606,7 @@ static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv) /* Sort all preceding sprites by order and assign new orders in reverse (as original sorter did). */ std::sort(preceding.begin(), preceding.end(), [](const ParentSpriteToDraw *a, const ParentSpriteToDraw *b) { - return a->order > b->order; + return a->order > b->order; }); s->order = ORDER_COMPARED; diff --git a/src/viewport_sprite_sorter_sse4.cpp b/src/viewport_sprite_sorter_sse4.cpp index ae51218181..f3747b89b2 100644 --- a/src/viewport_sprite_sorter_sse4.cpp +++ b/src/viewport_sprite_sorter_sse4.cpp @@ -55,7 +55,7 @@ void ViewportSortParentSpritesSSE41(ParentSpriteToSortVector *psdv) sprite_list.sort(); - std::vector preceding; // Temporarily stores sprites that precede current and their position in the list + std::vector preceding; // Temporarily stores sprites that precede current and their position in the list auto preceding_prev = sprite_list.begin(); // Store iterator in case we need to delete a single preciding sprite auto out = psdv->begin(); // Iterator to output sorted sprites @@ -151,7 +151,7 @@ void ViewportSortParentSpritesSSE41(ParentSpriteToSortVector *psdv) /* Sort all preceding sprites by order and assign new orders in reverse (as original sorter did). */ std::sort(preceding.begin(), preceding.end(), [](const ParentSpriteToDraw *a, const ParentSpriteToDraw *b) { - return a->order > b->order; + return a->order > b->order; }); s->order = ORDER_COMPARED; From 05df7996a4c8039db0785a7e22fe8db59bcc7513 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Fri, 5 Feb 2021 12:31:27 +0100 Subject: [PATCH 005/122] Feature: [Actions / CMake] support for generic linux builds (#8641) These bundles can be opened on any "modern" Linux machine with a driver that SDL2 supports. Machines needs at least glibc 2.15, which was released 10 years ago. It is build with CentOS 7 as base, and only assumes the following libraries are available on the system: - libc - libdl - libgcc_s - libpthread - librt - libstdc++ All other libraries the game depends on are bundled together with the game, so users don't need any library installed to use this bundle. The downside of course is that this increases the binary size a bit: 30 MiB of libraries are in this bundle. RPATH is used to make ld-linux find the folder libraries are stored in; however, system libraries are always used before these, in the assumption libraries on the user system are more up-to-date. Using -DOPTION_PACKAGE_DEPENDENCIES=ON switches on packaging of libraries in the "lib" folder. This requires CMake 3.16 to be installed; otherwise it will fail. --- .github/workflows/release.yml | 91 ++++++++++++++++++++++++++++++++++- CMakeLists.txt | 21 ++++++++ cmake/InstallAndPackage.cmake | 39 +++++++++++++-- cmake/Options.cmake | 5 +- 4 files changed, 149 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4003787fc5..1faaacfb3c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -265,7 +265,93 @@ jobs: retention-days: 5 linux: - name: Linux + name: Linux (Generic) + needs: source + + runs-on: ubuntu-20.04 + container: + # manylinux2014 is based on CentOS 7, but already has a lot of things + # installed and preconfigured. It makes it easier to build OpenTTD. + image: quay.io/pypa/manylinux2014_x86_64 + + steps: + - name: Download source + uses: actions/download-artifact@v2 + with: + name: internal-source + + - name: Unpack source + run: | + tar -xf source.tar.gz --strip-components=1 + + - name: Install dependencies + run: | + echo "::group::Install dependencies" + yum install -y \ + fontconfig-devel \ + freetype-devel \ + libicu-devel \ + libpng-devel \ + libpng-devel \ + lzo-devel \ + SDL2-devel \ + wget \ + xz-devel \ + zlib-devel \ + # EOF + echo "::endgroup::" + + # The yum variant of fluidsynth depends on all possible audio drivers. + # This is not really useful for us, as that would require a user to + # have them all before he can start OpenTTD. Instead, compile a + # version that can only use SDL2. As OpenTTD does sound via SDL2, + # this simply means we either have sound and music, or have none. + echo "::group::Install fluidsynth" + wget https://github.com/FluidSynth/fluidsynth/archive/v2.1.6.tar.gz + tar xf v2.1.6.tar.gz + ( + cd fluidsynth-2.1.6 + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=/usr + make -j$(nproc) + make install + ) + echo "::endgroup::" + + - name: Install GCC problem matcher + uses: ammaraskar/gcc-problem-matcher@master + + - name: Build + run: | + mkdir -p build + cd build + + echo "::group::CMake" + cmake ${GITHUB_WORKSPACE} \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DOPTION_PACKAGE_DEPENDENCIES=ON \ + # EOF + echo "::endgroup::" + + echo "::group::Build" + echo "Running on $(nproc) cores" + make -j$(nproc) package + echo "::endgroup::" + + # Remove the sha256 files CPack generates; we will do this ourself at + # the end of this workflow. + rm -f bundles/*.sha256 + + - name: Store bundles + uses: actions/upload-artifact@v2 + with: + name: openttd-linux-generic + path: build/bundles + retention-days: 5 + + linux-distro: + name: Linux (Distros) needs: source if: needs.source.outputs.is_tag == 'true' @@ -633,6 +719,7 @@ jobs: - source - docs - linux + - linux-distro - macos - windows @@ -641,7 +728,7 @@ jobs: # "always()" is important here, it is the keyword to use to stop skipping # this job if any dependency is skipped. It looks a bit silly, but it is # how GitHub Actions work ;) - if: always() && needs.source.result == 'success' && needs.docs.result == 'success' && (needs.linux.result == 'success' || needs.linux.result == 'skipped') && needs.macos.result == 'success' && needs.windows.result == 'success' + if: always() && needs.source.result == 'success' && needs.docs.result == 'success' && needs.linux.result == 'success' && (needs.linux-distro.result == 'success' || needs.linux-distro.result == 'skipped') && needs.macos.result == 'success' && needs.windows.result == 'success' runs-on: ubuntu-20.04 diff --git a/CMakeLists.txt b/CMakeLists.txt index d273937a31..1ac5be5bee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,27 @@ if(APPLE) endif() endif() +if(OPTION_PACKAGE_DEPENDENCIES) + if(NOT UNIX) + message(FATAL_ERROR "Can only package dependencies on Linux") + endif() + if(OPTION_INSTALL_FHS) + message(FATAL_ERROR "Cannot install in FHS folders when we are packaging dependencies") + endif() + if(${CMAKE_VERSION} VERSION_LESS "3.16.0") + message(FATAL_ERROR "OPTION_PACKAGE_DEPENDENCIES can only work with CMake 3.16+; you are using ${CMAKE_VERSION}") + endif() + + # If we are packaging dependencies, we do two things: + # 1) set the RPATH to include $ORIGIN/lib; $ORIGIN (that literal string) + # is a Linux indicator for "path where application is". In CMake, we + # have to do this before add_executable() is executed. + # 2) copy the libraries that we compile against to the "lib" folder. + # This is done in InstallAndPackage.cmake. + set(CMAKE_INSTALL_RPATH "\$ORIGIN/lib") + set(CMAKE_BUILD_WITH_INSTALL_RPATH ON) +endif() + include(SourceList) # Needed by rev.cpp diff --git a/cmake/InstallAndPackage.cmake b/cmake/InstallAndPackage.cmake index b54c8131ac..5c56d2d6d5 100644 --- a/cmake/InstallAndPackage.cmake +++ b/cmake/InstallAndPackage.cmake @@ -123,8 +123,12 @@ elseif(UNIX) # With FHS, we can create deb/rpm/... Without it, they would be horribly broken # and not work. The other way around is also true; with FHS they are not # usable, and only flat formats work. - if(NOT OPTION_INSTALL_FHS) + if(OPTION_PACKAGE_DEPENDENCIES) set(CPACK_GENERATOR "TXZ") + set(PLATFORM "generic") + elseif(NOT OPTION_INSTALL_FHS) + set(CPACK_GENERATOR "TXZ") + set(PLATFORM "unknown") else() find_program(LSB_RELEASE_EXEC lsb_release) execute_process(COMMAND ${LSB_RELEASE_EXEC} -is @@ -149,7 +153,7 @@ elseif(UNIX) string(REGEX MATCH "ID=(.*)" _ ${OS_RELEASE_CONTENTS}) set(DISTRO_ID ${CMAKE_MATCH_1}) if(DISTRO_ID STREQUAL "arch") - set(PLATFORM "generic") + set(PLATFORM "arch") set(CPACK_GENERATOR "TXZ") else() set(UNSUPPORTED_PLATFORM_NAME "Linux distribution '${DISTRO_ID}' from /etc/os-release") @@ -163,12 +167,39 @@ elseif(UNIX) set(CPACK_GENERATOR "TXZ") message(WARNING "Unknown ${UNSUPPORTED_PLATFORM_NAME} found for packaging; can only pack to a txz. Please consider creating a Pull Request to add support for this distribution.") endif() - - set(CPACK_PACKAGE_FILE_NAME "openttd-#CPACK_PACKAGE_VERSION#-linux-${PLATFORM}-${CPACK_SYSTEM_NAME}") endif() + set(CPACK_PACKAGE_FILE_NAME "openttd-#CPACK_PACKAGE_VERSION#-linux-${PLATFORM}-${CPACK_SYSTEM_NAME}") + else() message(FATAL_ERROR "Unknown OS found for packaging; please consider creating a Pull Request to add support for this OS.") endif() include(CPack) + +if(OPTION_PACKAGE_DEPENDENCIES) + # Install all dependencies we can resolve, with the exception of ones that + # every Linux machine should have. This should make this build as generic + # as possible, where it runs on any machine with the same or newer libc + # than the one this is compiled with. + # We copy these libraries into lib/ folder, so they can be found on game + # startup. See comment in root CMakeLists.txt for how this works exactly. + install(CODE [[ + file(GET_RUNTIME_DEPENDENCIES + RESOLVED_DEPENDENCIES_VAR DEPENDENCIES + UNRESOLVED_DEPENDENCIES_VAR UNRESOLVED_DEPENDENCIES + EXECUTABLES openttd + POST_EXCLUDE_REGEXES "ld-linux|libc.so|libdl.so|libm.so|libgcc_s.so|libpthread.so|librt.so|libstdc...so") + file(INSTALL + DESTINATION "${CMAKE_INSTALL_PREFIX}/lib" + FILES ${DEPENDENCIES} + FOLLOW_SYMLINK_CHAIN) + + # This should not be possible, but error out when a dependency cannot + # be resolved. + list(LENGTH UNRESOLVED_DEPENDENCIES UNRESOLVED_LENGTH) + if(${UNRESOLVED_LENGTH} GREATER 0) + message(FATAL_ERROR "Unresolved dependencies: ${UNRESOLVED_DEPENDENCIES}") + endif() + ]]) +endif() diff --git a/cmake/Options.cmake b/cmake/Options.cmake index c94a193b32..42d1127906 100644 --- a/cmake/Options.cmake +++ b/cmake/Options.cmake @@ -44,7 +44,9 @@ endfunction() # set_options() # function(set_options) - if(UNIX AND NOT APPLE) + option(OPTION_PACKAGE_DEPENDENCIES "Copy dependencies into lib/ for easy packaging (Linux only)" OFF) + + if(UNIX AND NOT APPLE AND NOT OPTION_PACKAGE_DEPENDENCIES) set(DEFAULT_OPTION_INSTALL_FHS ON) else() set(DEFAULT_OPTION_INSTALL_FHS OFF) @@ -76,6 +78,7 @@ endfunction() # show_options() # function(show_options) + message(STATUS "Option Package Dependencies - ${OPTION_PACKAGE_DEPENDENCIES}") message(STATUS "Option Dedicated - ${OPTION_DEDICATED}") message(STATUS "Option Install FHS - ${OPTION_INSTALL_FHS}") message(STATUS "Option Use assert - ${OPTION_USE_ASSERTS}") From a667ed945f928a605a7e4c636d62d47eeb8dc6aa Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Fri, 5 Feb 2021 20:58:15 +0100 Subject: [PATCH 006/122] Add: [Actions] Automatically upload releases to Steam (#8644) --- .github/workflows/release.yml | 79 ++++++++++++++++++++++++++++++++++- os/steam/release.vdf | 57 +++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 os/steam/release.vdf diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1faaacfb3c..d759d10e34 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -714,7 +714,7 @@ jobs: retention-days: 5 upload: - name: Upload + name: Upload (AWS) needs: - source - docs @@ -772,3 +772,80 @@ jobs: repository: OpenTTD/workflows event-type: ${{ needs.source.outputs.trigger_type }} client-payload: '{"version": "${{ needs.source.outputs.version }}", "folder": "${{ needs.source.outputs.folder }}"}' + + upload-steam: + name: Upload (Steam) + needs: + - source + - linux + - macos + - windows + + if: needs.source.outputs.trigger_type == 'new-master' || needs.source.outputs.trigger_type == 'new-tag' + + runs-on: ubuntu-20.04 + + steps: + - name: Download all bundles + uses: actions/download-artifact@v2 + + - name: Setup steamcmd + uses: CyberAndrii/setup-steamcmd@v1 + + - name: Generate Steam auth code + id: steam-totp + uses: CyberAndrii/steam-totp@v1 + with: + shared_secret: ${{ secrets.STEAM_SHARED_SECRET }} + + - name: Upload to Steam + run: | + echo "::group::Extracting source" + mkdir source + ( + cd source + tar -xf ../internal-source/source.tar.gz --strip-components=1 + ) + echo "::endgroup::" + + mkdir steam + ( + cd steam + + echo "::group::Prepare Win32" + unzip ../openttd-windows-x86/openttd-*-windows-win32.zip + mv openttd-*-windows-win32 steam-win32 + echo "::endgroup::" + + echo "::group::Prepare Win64" + unzip ../openttd-windows-x64/openttd-*-windows-win64.zip + mv openttd-*-windows-win64 steam-win64 + echo "::endgroup::" + + echo "::group::Prepare macOS" + mkdir steam-macos + ( + cd steam-macos + unzip ../../openttd-macos-universal/openttd-*-macos-universal.zip + ) + echo "::endgroup::" + + echo "::group::Prepare Linux" + tar xvf ../openttd-linux-generic/openttd-*-linux-generic-amd64.tar.xz + mv openttd-*-linux-generic-amd64 steam-linux + echo "::endgroup::" + + echo "::group::Preparing build file" + if [ "${{ needs.source.outputs.trigger_type }}" = "new-tag" ]; then + BRANCH="testing" + else + BRANCH="nightly" + fi + cat ../source/os/steam/release.vdf | sed 's/@@DESCRIPTION@@/openttd-${{ needs.source.outputs.version }}/;s/@@BRANCH@@/'${BRANCH}'/' > release.vdf + cat release.vdf + echo "::endgroup::" + + echo "::group::Upload to Steam" + steamcmd +login ${{ secrets.STEAM_USERNAME }} ${{ secrets.STEAM_PASSWORD }} ${{ steps.steam-totp.outputs.code }} +run_app_build $(pwd)/release.vdf +quit + echo "::endgroup::" + ) diff --git a/os/steam/release.vdf b/os/steam/release.vdf new file mode 100644 index 0000000000..ab549c3886 --- /dev/null +++ b/os/steam/release.vdf @@ -0,0 +1,57 @@ +"AppBuild" +{ + "AppID" "1536610" + "Desc" "@@DESCRIPTION@@" + + "SetLive" "@@BRANCH@@" + + "ContentRoot" "./" + "BuildOutput" "build/" + + "Depots" + { + "1536613" + { + "ContentRoot" "./steam-win32" + "FileMapping" + { + "LocalPath" "*" + "DepotPath" "." + "recursive" "1" + } + } + + "1536612" + { + "ContentRoot" "./steam-win64" + "FileMapping" + { + "LocalPath" "*" + "DepotPath" "." + "recursive" "1" + } + } + + "1536614" + { + "ContentRoot" "./steam-macos" + "FileMapping" + { + "LocalPath" "*" + "DepotPath" "." + "recursive" "1" + } + } + + "1536615" + { + "ContentRoot" "./steam-linux" + "FileMapping" + { + "LocalPath" "*" + "DepotPath" "." + "recursive" "1" + } + } + } +} From 99448eedca8f3759b8de44c356f3947dbd6e448b Mon Sep 17 00:00:00 2001 From: glx22 Date: Fri, 5 Feb 2021 22:31:33 +0100 Subject: [PATCH 007/122] Fix: [CMake] os/windows/openttd.manifest is not a generated file --- CMakeLists.txt | 5 +++++ cmake/CompileFlags.cmake | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ac5be5bee..2fb7a2309a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,6 +201,11 @@ add_executable(openttd WIN32 ${GENERATED_SOURCE_FILES}) set_target_properties(openttd PROPERTIES OUTPUT_NAME "${BINARY_NAME}") # All other files are added via target_sources() +if(MSVC) + # Add DPI manifest to project; other WIN32 targets get this via ottdres.rc + target_sources(openttd PRIVATE "${CMAKE_SOURCE_DIR}/os/windows/openttd.manifest") +endif() + add_subdirectory(${CMAKE_SOURCE_DIR}/src) add_subdirectory(${CMAKE_SOURCE_DIR}/media/baseset) diff --git a/cmake/CompileFlags.cmake b/cmake/CompileFlags.cmake index 772041a42c..f4047b8396 100644 --- a/cmake/CompileFlags.cmake +++ b/cmake/CompileFlags.cmake @@ -27,9 +27,6 @@ macro(compile_flags) # Enable multi-threaded compilation. add_compile_options(/MP) endif() - - # Add DPI manifest to project; other WIN32 targets get this via ottdres.rc - list(APPEND GENERATED_SOURCE_FILES "${CMAKE_SOURCE_DIR}/os/windows/openttd.manifest") endif() # Add some -D flags for Debug builds. We cannot use add_definitions(), because From 4f0692c437c7c89b38134255a5b7aea9f0378c19 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Fri, 5 Feb 2021 23:56:44 +0100 Subject: [PATCH 008/122] Codechange: [Actions] Document better why we build our own fluidsynth (#8646) nielsmh nicely correct us in #8641, pointing out the old comment is not telling a complete truth. The result is the same, but it is better to not mislead future-us. --- .github/workflows/release.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d759d10e34..63af9ff872 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -301,11 +301,13 @@ jobs: # EOF echo "::endgroup::" - # The yum variant of fluidsynth depends on all possible audio drivers. - # This is not really useful for us, as that would require a user to - # have them all before he can start OpenTTD. Instead, compile a - # version that can only use SDL2. As OpenTTD does sound via SDL2, - # this simply means we either have sound and music, or have none. + # The yum variant of fluidsynth depends on all possible audio drivers, + # like jack, ALSA, pulseaudio, etc. This is not really useful for us, + # as we route the output of fluidsynth back via our sound driver, and + # as such do not use these audio driver outputs at all. So instead, + # we compile fluidsynth ourselves, with as little dependencies as + # possible. This currently means it picks up SDL2, but this is fine, + # as we need SDL2 anyway. echo "::group::Install fluidsynth" wget https://github.com/FluidSynth/fluidsynth/archive/v2.1.6.tar.gz tar xf v2.1.6.tar.gz From 2c9084d48cf1a3ad3c5f8d3bcad83c396e8d29fd Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sat, 6 Feb 2021 14:09:45 +0100 Subject: [PATCH 009/122] Fix #8029: [SDL2] disable draw-thread on wayland SDL video driver (#8648) When the wayland SDL video driver is used, an EGL context is created in the main thread. It is not allowed to update this context from another thread, which is exactly what our draw-thread is trying. The other solution would be to move all of SDL into the draw-thread, but that would introduce a whole scala of different problems. The wayland SDL backend is significantly faster than the X11 SDL backend, but there is a performance hit nevertheless. --- src/video/sdl2_v.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index 66e6586fdc..fd6cf6c112 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -715,6 +715,17 @@ const char *VideoDriver_SDL::Start(const StringList &parm) MarkWholeScreenDirty(); _draw_threaded = !GetDriverParamBool(parm, "no_threads") && !GetDriverParamBool(parm, "no_thread"); + /* Wayland SDL video driver uses EGL to render the game. SDL created the + * EGL context from the main-thread, and with EGL you are not allowed to + * draw in another thread than the context was created. The function of + * _draw_threaded is to do exactly this: draw in another thread than the + * window was created, and as such, this fails on Wayland SDL video + * driver. So, we disable threading by default if Wayland SDL video + * driver is detected. + */ + if (strcmp(dname, "wayland") == 0) { + _draw_threaded = false; + } SDL_StopTextInput(); this->edit_box_focused = false; From 9322b40df1bf0987a440404fbb2c72dfb4410ba3 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 7 Feb 2021 12:54:02 +0100 Subject: [PATCH 010/122] Fix: [CMake] our allegro drivers use v4, so skip v5 if found (#8653) On some distros allegro v5 is called allegro-5, but on some others it is not. So this should fix for all distros that allegro v5 is not being picked up, and only v4 is. --- cmake/FindAllegro.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FindAllegro.cmake b/cmake/FindAllegro.cmake index 3c90d2c4e7..5d873dd3e1 100644 --- a/cmake/FindAllegro.cmake +++ b/cmake/FindAllegro.cmake @@ -31,7 +31,7 @@ The following cache variables may also be set: #]=======================================================================] find_package(PkgConfig QUIET) -pkg_check_modules(PC_Allegro QUIET allegro) +pkg_check_modules(PC_Allegro QUIET allegro<5) find_path(Allegro_INCLUDE_DIR NAMES allegro.h From 2a6da319b2578944b2ac6b1a267bf880e5c4d34c Mon Sep 17 00:00:00 2001 From: Matt Kimber Date: Sun, 7 Feb 2021 15:15:46 +0000 Subject: [PATCH 011/122] Fix 0125892: Don't crash when towns upgrade road tiles during expansion (#8651) --- src/road_cmd.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index c94beb0b16..b476416899 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -2291,7 +2291,7 @@ static bool CanConvertUnownedRoadType(Owner owner, RoadTramType rtt) } /** - * Convert the ownership of the RoadType of the tile if applyable + * Convert the ownership of the RoadType of the tile if applicable * @param tile the tile of which convert ownership * @param num_pieces the count of the roadbits to assign to the new owner * @param owner the current owner of the RoadType @@ -2428,11 +2428,9 @@ CommandCost CmdConvertRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 cost.AddCost(num_pieces * RoadConvertCost(from_type, to_type)); if (flags & DC_EXEC) { // we can safely convert, too - /* Update the company infrastructure counters. */ + /* Call ConvertRoadTypeOwner() to update the company infrastructure counters. */ if (owner == _current_company) { - Company * c = Company::Get(_current_company); - c->infrastructure.road[from_type] -= num_pieces; - c->infrastructure.road[to_type] += num_pieces; + ConvertRoadTypeOwner(tile, num_pieces, owner, from_type, to_type); } /* Perform the conversion */ From f7ac2969ef4eae88ef61ec095b0ad081e79168aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Guilloux?= Date: Sun, 7 Feb 2021 16:19:30 +0100 Subject: [PATCH 012/122] Fix: [CMake] Restore 'games' as default install bindir (#8629) --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fb7a2309a..be544316dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,10 +23,13 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9) # Use GNUInstallDirs to allow customisation -# but set our own default data dir +# but set our own default data and bin dir if(NOT CMAKE_INSTALL_DATADIR) set(CMAKE_INSTALL_DATADIR "share/games") endif() +if(NOT CMAKE_INSTALL_BINDIR) + set(CMAKE_INSTALL_BINDIR "games") +endif() include(GNUInstallDirs) include(Options) From b927da73c1c2e354731b410e02f4b98b1b311ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Guilloux?= Date: Sun, 7 Feb 2021 16:20:26 +0100 Subject: [PATCH 013/122] Fix 81d335b081: Use non-pulsating red highlight for coverage (#8622) --- src/viewport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/viewport.cpp b/src/viewport.cpp index 822b0db9c2..2a3f9b32de 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -1030,7 +1030,7 @@ static void DrawTileHighlightType(const TileInfo *ti, TileHighlightType tht) case THT_NONE: break; case THT_WHITE: DrawTileSelectionRect(ti, PAL_NONE); break; case THT_BLUE: DrawTileSelectionRect(ti, PALETTE_SEL_TILE_BLUE); break; - case THT_RED: DrawTileSelectionRect(ti, PALETTE_TILE_RED_PULSATING); break; + case THT_RED: DrawTileSelectionRect(ti, PALETTE_SEL_TILE_RED); break; } } From 2cbfcd232758967fa84794967d71d9584ec799ec Mon Sep 17 00:00:00 2001 From: glx22 Date: Fri, 5 Feb 2021 02:00:36 +0100 Subject: [PATCH 014/122] Change: [CMake] Bump minimum version to 3.9 --- CMakeLists.txt | 11 +++-------- cmake/FindICU.cmake | 5 +++++ src/settingsgen/CMakeLists.txt | 2 +- src/strgen/CMakeLists.txt | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index be544316dd..8fbf3507d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.9) if(NOT BINARY_NAME) set(BINARY_NAME openttd) @@ -143,13 +143,8 @@ find_package(Xaudio2) find_package(Grfcodec) -# IPO is only properly supported from CMake 3.9. Despite the fact we are -# CMake 3.5, still enable IPO if we detect we are 3.9+. -if(POLICY CMP0069) - cmake_policy(SET CMP0069 NEW) - include(CheckIPOSupported) - check_ipo_supported(RESULT IPO_FOUND) -endif() +include(CheckIPOSupported) +check_ipo_supported(RESULT IPO_FOUND) show_options() diff --git a/cmake/FindICU.cmake b/cmake/FindICU.cmake index b110dbf404..d12f36b0ad 100644 --- a/cmake/FindICU.cmake +++ b/cmake/FindICU.cmake @@ -1,3 +1,8 @@ +# CMake provides a FindICU module since version 3.7. +# But it doesn't use pkgconfig, doesn't set expected variables, +# And it returns incomplete dependencies if only some modules are searched. + + #[=======================================================================[.rst: FindICU ------- diff --git a/src/settingsgen/CMakeLists.txt b/src/settingsgen/CMakeLists.txt index 69b2092017..43d5284e62 100644 --- a/src/settingsgen/CMakeLists.txt +++ b/src/settingsgen/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.9) if (NOT HOST_BINARY_DIR) project(settingsgen) diff --git a/src/strgen/CMakeLists.txt b/src/strgen/CMakeLists.txt index 08c1223350..490f675f2e 100644 --- a/src/strgen/CMakeLists.txt +++ b/src/strgen/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.9) if (NOT HOST_BINARY_DIR) project(strgen) From af0acc9a75c4399d5aa330a811e0706be20b5995 Mon Sep 17 00:00:00 2001 From: glx22 Date: Fri, 5 Feb 2021 16:10:42 +0100 Subject: [PATCH 015/122] Update: Specify CMake minimum version in COMPILING.md Also give some hints for MSVC users. --- COMPILING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/COMPILING.md b/COMPILING.md index 145b244753..5e642dc874 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -57,6 +57,9 @@ To install both the x64 (64bit) and x86 (32bit) variants (though only one is nec ``` You can open the folder (as a CMake project). CMake will be detected, and you can compile from there. +If libraries are installed but not found, you need to set VCPKG_TARGET_TRIPLET in CMake parameters. +For Visual Studio 2017 you also need to set CMAKE_TOOLCHAIN_FILE. +(Typical values are shown in the MSVC project file command line example) Alternatively, you can create a MSVC project file via CMake. For this either download CMake from https://cmake.org/download/ or use the version @@ -73,6 +76,7 @@ in the build folder are MSVC project files. MSVC can rebuild the project files himself via the `ZERO_CHECK` project. ## All other platforms +Minimum required version of CMake is 3.9. ```bash mkdir build From da4c404f3d9be98427d292dd2dd707573aa4a68f Mon Sep 17 00:00:00 2001 From: Owen Rudge Date: Mon, 8 Feb 2021 15:39:53 +0000 Subject: [PATCH 016/122] Fix: [Actions] Use same vcpkg commit for CI on macOS as release builds --- .github/workflows/ci-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 8b9a160d96..9833d5e512 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -159,7 +159,8 @@ jobs: uses: lukka/run-vcpkg@v6 with: vcpkgDirectory: '/usr/local/share/vcpkg' - doNotUpdateVcpkg: true + doNotUpdateVcpkg: false + vcpkgGitCommitId: 2a42024b53ebb512fb5dd63c523338bf26c8489c vcpkgArguments: 'freetype liblzma lzo' vcpkgTriplet: '${{ matrix.arch }}-osx' From ac2b5e57cf2805eb24370b664380322d62a04eff Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 8 Feb 2021 19:07:34 +0100 Subject: [PATCH 017/122] Fix: mention our websites with https:// (instead of http://) (#8657) It is 2021. Nobody should advertise http anymore. Not even us. --- src/misc_gui.cpp | 2 +- src/network/network_content_gui.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index e5da5f3a12..9d0259b3be 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -510,7 +510,7 @@ struct AboutWindow : public Window { void SetStringParameters(int widget) const override { - if (widget == WID_A_WEBSITE) SetDParamStr(0, "Website: http://www.openttd.org"); + if (widget == WID_A_WEBSITE) SetDParamStr(0, "Website: https://www.openttd.org"); if (widget == WID_A_COPYRIGHT) SetDParamStr(0, _openttd_revision_year); } diff --git a/src/network/network_content_gui.cpp b/src/network/network_content_gui.cpp index 06e57a2ac2..13e162b07a 100644 --- a/src/network/network_content_gui.cpp +++ b/src/network/network_content_gui.cpp @@ -325,7 +325,7 @@ class NetworkContentListWindow : public Window, ContentCallback { char url[1024]; const char *last = lastof(url); - char *pos = strecpy(url, "http://grfsearch.openttd.org/?", last); + char *pos = strecpy(url, "https://grfsearch.openttd.org/?", last); if (this->auto_select) { pos = strecpy(pos, "do=searchgrfid&q=", last); From 6c8f2227cdc01c978ec1cbf08a93925e148175b8 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Mon, 8 Feb 2021 13:18:30 -0500 Subject: [PATCH 018/122] Fix: [Emscripten] open links in browser (#8655) --- os/emscripten/pre.js | 28 ++++++++++++++++++++++++++++ src/os/unix/unix.cpp | 12 +++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/os/emscripten/pre.js b/os/emscripten/pre.js index 5cbd899e04..1563e4f95b 100644 --- a/os/emscripten/pre.js +++ b/os/emscripten/pre.js @@ -71,6 +71,34 @@ Module.preRun.push(function() { * add_server("localhost", 3979); */ } + var leftButtonDown = false; + document.addEventListener("mousedown", e => { + if (e.button == 0) { + leftButtonDown = true; + } + }); + document.addEventListener("mouseup", e => { + if (e.button == 0) { + leftButtonDown = false; + } + }); + window.openttd_open_url = function(url, url_len) { + const url_string = UTF8ToString(url, url_len); + function openWindow() { + document.removeEventListener("mouseup", openWindow); + window.open(url_string, '_blank'); + } + /* Trying to open the URL while the mouse is down results in the button getting stuck, so wait for the + * mouse to be released before opening it. However, when OpenTTD is lagging, the mouse can get released + * before the button click even registers, so check for that, and open the URL immediately if that's the + * case. */ + if (leftButtonDown) { + document.addEventListener("mouseup", openWindow); + } else { + openWindow(); + } + } + /* https://github.com/emscripten-core/emscripten/pull/12995 implements this * properly. Till that time, we use a polyfill. */ SOCKFS.websocket_sock_ops.createPeer_ = SOCKFS.websocket_sock_ops.createPeer; diff --git a/src/os/unix/unix.cpp b/src/os/unix/unix.cpp index 4b94539b03..9ef2bc386d 100644 --- a/src/os/unix/unix.cpp +++ b/src/os/unix/unix.cpp @@ -28,6 +28,10 @@ #include #endif +#ifdef __EMSCRIPTEN__ +# include +#endif + #ifdef __APPLE__ # include #elif (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L) || defined(__GLIBC__) @@ -288,7 +292,13 @@ bool GetClipboardContents(char *buffer, const char *last) #endif -#ifndef __APPLE__ +#if defined(__EMSCRIPTEN__) +void OSOpenBrowser(const char *url) +{ + /* Implementation in pre.js */ + EM_ASM({ if(window["openttd_open_url"]) window.openttd_open_url($0, $1) }, url, strlen(url)); +} +#elif !defined( __APPLE__) void OSOpenBrowser(const char *url) { pid_t child_pid = fork(); From 395e015282279e95910128ebcd221879a22f9421 Mon Sep 17 00:00:00 2001 From: Owen Rudge Date: Sat, 30 Jan 2021 09:36:20 +0000 Subject: [PATCH 019/122] Change: Convert .md to .rtf for Windows/Mac packages --- .github/workflows/release.yml | 12 ++++++++++++ CMakeLists.txt | 4 ++++ cmake/FindPandoc.cmake | 3 +++ cmake/InstallAndPackage.cmake | 9 ++++++++- 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 cmake/FindPandoc.cmake diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63af9ff872..07feba9186 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -461,6 +461,13 @@ jobs: run: | tar -xf source.tar.gz --strip-components=1 + - name: Install dependencies + env: + HOMEBREW_NO_AUTO_UPDATE: 1 + HOMEBREW_NO_INSTALL_CLEANUP: 1 + run: | + brew install pandoc + # The following step can be removed when the build VM is updated with a revision of # vcpkg dating from roughly 01/01/2021 or later. At that point, `doNotUpdateVcpkg` # can be set to `true` and the `vcpkgGitCommitId` can be removed. @@ -638,6 +645,11 @@ jobs: run: | tar -xf source.tar.gz --strip-components=1 + - name: Install dependencies + shell: bash + run: | + choco install pandoc + # "restore-cache" which is done by "run-vcpkg" uses Windows tar. # A git clone on windows marks a few files as read-only; when Windows tar # tries to extract the cache over this folder, it fails, despite the files diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fbf3507d0..c5d7ac1137 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,4 +337,8 @@ endif() include(CreateRegression) create_regression() +if(APPLE OR WIN32) + find_package(Pandoc) +endif() + include(InstallAndPackage) diff --git a/cmake/FindPandoc.cmake b/cmake/FindPandoc.cmake new file mode 100644 index 0000000000..3a42851dd7 --- /dev/null +++ b/cmake/FindPandoc.cmake @@ -0,0 +1,3 @@ +if(NOT EXISTS ${PANDOC_EXECUTABLE}) + find_program(PANDOC_EXECUTABLE pandoc) +endif() diff --git a/cmake/InstallAndPackage.cmake b/cmake/InstallAndPackage.cmake index 5c56d2d6d5..5fd17cb037 100644 --- a/cmake/InstallAndPackage.cmake +++ b/cmake/InstallAndPackage.cmake @@ -95,7 +95,14 @@ set(CPACK_PACKAGE_HOMEPAGE_URL "https://www.openttd.org/") set(CPACK_PACKAGE_CONTACT "OpenTTD ") set(CPACK_PACKAGE_INSTALL_DIRECTORY "OpenTTD") set(CPACK_PACKAGE_CHECKSUM "SHA256") -set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING.md") + +if((APPLE OR WIN32) AND EXISTS ${PANDOC_EXECUTABLE}) + execute_process(COMMAND ${PANDOC_EXECUTABLE} "${CMAKE_SOURCE_DIR}/COPYING.md" -s -o "${CMAKE_BINARY_DIR}/COPYING.rtf") + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_BINARY_DIR}/COPYING.rtf") +else() + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING.md") +endif() + set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md") set(CPACK_MONOLITHIC_INSTALL YES) set(CPACK_PACKAGE_EXECUTABLES "openttd;OpenTTD") From f1f281b318dc562d5741b55ff25198f3f1602312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Guilloux?= Date: Tue, 9 Feb 2021 21:21:57 +0100 Subject: [PATCH 020/122] Fix: [Win32] Set minimum resolution for timers to 1ms. (#8660) --- src/os/windows/crashlog_win.cpp | 4 ++++ src/os/windows/win32.cpp | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/os/windows/crashlog_win.cpp b/src/os/windows/crashlog_win.cpp index 872e472d5e..b5916dd261 100644 --- a/src/os/windows/crashlog_win.cpp +++ b/src/os/windows/crashlog_win.cpp @@ -21,6 +21,7 @@ #include #include +#include #include "../../safeguards.h" @@ -540,6 +541,9 @@ void *_safe_esp = nullptr; static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep) { + /* Restore system timer resolution. */ + timeEndPeriod(1); + /* Disable our event loop. */ SetWindowLongPtr(GetActiveWindow(), GWLP_WNDPROC, (LONG_PTR)&DefWindowProc); diff --git a/src/os/windows/win32.cpp b/src/os/windows/win32.cpp index 6576ea20d7..8783b3762d 100644 --- a/src/os/windows/win32.cpp +++ b/src/os/windows/win32.cpp @@ -18,6 +18,7 @@ #define NO_SHOBJIDL_SORTDIRECTION // Avoid multiple definition of SORT_ASCENDING #include /* SHGetFolderPath */ #include +#include #include "win32.h" #include "../../fios.h" #include "../../core/alloc_func.hpp" @@ -413,6 +414,9 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi int argc; char *argv[64]; // max 64 command line arguments + /* Set system timer resolution to 1ms. */ + timeBeginPeriod(1); + CrashLog::InitialiseCrashLog(); #if defined(UNICODE) @@ -440,6 +444,10 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi for (int i = 0; i < argc; i++) ValidateString(argv[i]); openttd_main(argc, argv); + + /* Restore system timer resolution. */ + timeEndPeriod(1); + free(cmdline); return 0; } From 83ddb1501fe059241ef11e88f14bcfb1dafa9b72 Mon Sep 17 00:00:00 2001 From: Kuhnovic <68320206+Kuhnovic@users.noreply.github.com> Date: Wed, 10 Feb 2021 16:35:50 +0100 Subject: [PATCH 021/122] Feature: Remove all industries button in scenario editor (#8550) --- src/industry_gui.cpp | 85 +++++++++++++++++++++++++++-------- src/lang/english.txt | 8 +++- src/widgets/industry_widget.h | 13 +++--- 3 files changed, 81 insertions(+), 25 deletions(-) diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp index 58f4a6097a..f3d61ec6d6 100644 --- a/src/industry_gui.cpp +++ b/src/industry_gui.cpp @@ -37,6 +37,7 @@ #include "smallmap_gui.h" #include "widgets/dropdown_type.h" #include "widgets/industry_widget.h" +#include "clear_map.h" #include "table/strings.h" @@ -244,6 +245,14 @@ static const NWidgetPart _nested_build_industry_widgets[] = { NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN), NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN), EndContainer(), + NWidget(NWID_SELECTION, COLOUR_DARK_GREEN, WID_DPI_SCENARIO_EDITOR_PANE), + NWidget(NWID_VERTICAL), + NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_CREATE_RANDOM_INDUSTRIES_WIDGET), SetMinimalSize(0, 12), SetFill(1, 0), SetResize(1, 0), + SetDataTip(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_DPI_REMOVE_ALL_INDUSTRIES_WIDGET), SetMinimalSize(0, 12), SetFill(1, 0), SetResize(1, 0), + SetDataTip(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES, STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP), + EndContainer(), + EndContainer(), NWidget(NWID_HORIZONTAL), NWidget(WWT_MATRIX, COLOUR_DARK_GREEN, WID_DPI_MATRIX_WIDGET), SetMatrixDataTip(1, 0, STR_FUND_INDUSTRY_SELECTION_TOOLTIP), SetFill(1, 0), SetResize(1, 1), SetScrollbar(WID_DPI_SCROLLBAR), NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_DPI_SCROLLBAR), @@ -289,11 +298,6 @@ class BuildIndustryWindow : public Window { this->enabled[i] = false; } - if (_game_mode == GM_EDITOR) { // give room for the Many Random "button" - this->index[this->count] = INVALID_INDUSTRYTYPE; - this->enabled[this->count] = true; - this->count++; - } /* Fill the arrays with industries. * The tests performed after the enabled allow to load the industries * In the same way they are inserted by grf (if any) @@ -392,6 +396,13 @@ public: this->FinishInitNested(0); this->SetButtons(); + + /* Show scenario editor tools in editor. */ + if (_game_mode != GM_EDITOR) { + auto *se_tools = this->GetWidget(WID_DPI_SCENARIO_EDITOR_PANE); + se_tools->SetDisplayedPlane(SZSP_HORIZONTAL); + this->ReInit(); + } } void OnInit() override @@ -578,9 +589,53 @@ public: } } + static void AskManyRandomIndustriesCallback(Window *w, bool confirmed) + { + if (!confirmed) return; + + if (Town::GetNumItems() == 0) { + ShowErrorMessage(STR_ERROR_CAN_T_GENERATE_INDUSTRIES, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO); + } else { + extern void GenerateIndustries(); + _generating_world = true; + GenerateIndustries(); + _generating_world = false; + } + } + + static void AskRemoveAllIndustriesCallback(Window *w, bool confirmed) + { + if (!confirmed) return; + + for (Industry* industry : Industry::Iterate()) delete industry; + + /* Clear farmland. */ + for (TileIndex tile = 0; tile < MapSize(); tile++) { + if (IsTileType(tile, MP_CLEAR) && GetRawClearGround(tile) == CLEAR_FIELDS) { + MakeClear(tile, CLEAR_GRASS, 3); + } + } + + MarkWholeScreenDirty(); + } + void OnClick(Point pt, int widget, int click_count) override { switch (widget) { + case WID_DPI_CREATE_RANDOM_INDUSTRIES_WIDGET: { + assert(_game_mode == GM_EDITOR); + this->HandleButtonClick(WID_DPI_CREATE_RANDOM_INDUSTRIES_WIDGET); + ShowQuery(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY, nullptr, AskManyRandomIndustriesCallback); + break; + } + + case WID_DPI_REMOVE_ALL_INDUSTRIES_WIDGET: { + assert(_game_mode == GM_EDITOR); + this->HandleButtonClick(WID_DPI_REMOVE_ALL_INDUSTRIES_WIDGET); + ShowQuery(STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION, STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY, nullptr, AskRemoveAllIndustriesCallback); + break; + } + case WID_DPI_MATRIX_WIDGET: { int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_DPI_MATRIX_WIDGET); if (y < this->count) { // Is it within the boundaries of available data? @@ -610,22 +665,14 @@ public: break; case WID_DPI_FUND_WIDGET: { - if (this->selected_type == INVALID_INDUSTRYTYPE) { - this->HandleButtonClick(WID_DPI_FUND_WIDGET); - - if (Town::GetNumItems() == 0) { - ShowErrorMessage(STR_ERROR_CAN_T_GENERATE_INDUSTRIES, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO); + if (this->selected_type != INVALID_INDUSTRYTYPE) + { + if (_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && GetIndustrySpec(this->selected_type)->IsRawIndustry()) { + DoCommandP(0, this->selected_type, InteractiveRandom(), CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY)); + this->HandleButtonClick(WID_DPI_FUND_WIDGET); } else { - extern void GenerateIndustries(); - _generating_world = true; - GenerateIndustries(); - _generating_world = false; + HandlePlacePushButton(this, WID_DPI_FUND_WIDGET, SPR_CURSOR_INDUSTRY, HT_RECT); } - } else if (_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && GetIndustrySpec(this->selected_type)->IsRawIndustry()) { - DoCommandP(0, this->selected_type, InteractiveRandom(), CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY)); - this->HandleButtonClick(WID_DPI_FUND_WIDGET); - } else { - HandlePlacePushButton(this, WID_DPI_FUND_WIDGET, SPR_CURSOR_INDUSTRY, HT_RECT); } break; } diff --git a/src/lang/english.txt b/src/lang/english.txt index dc184aa317..567d238203 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2605,12 +2605,18 @@ STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM :{BLACK}Random # Fund new industry window STR_FUND_INDUSTRY_CAPTION :{WHITE}Fund new industry STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}Choose the appropriate industry from this list -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :Many random industries +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :{BLACK}Create random industries STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Cover the map with randomly placed industries +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION :{WHITE}Create random industries +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY :{YELLOW}Are you sure you want to create many random industries? STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST :{BLACK}Cost: {YELLOW}{CURRENCY_LONG} STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY :{BLACK}Prospect STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}Build STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}Fund +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES :{BLACK}Remove all industries +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP :{BLACK}Remove all industries currently present on the map +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION :{WHITE}Remove all industries +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY :{YELLOW}Are you sure you want to remove all industries? # Industry cargoes window STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION :{WHITE}Industry chain for {STRING} industry diff --git a/src/widgets/industry_widget.h b/src/widgets/industry_widget.h index e9fb2b1148..5e3a656c1f 100644 --- a/src/widgets/industry_widget.h +++ b/src/widgets/industry_widget.h @@ -12,11 +12,14 @@ /** Widgets of the #BuildIndustryWindow class. */ enum DynamicPlaceIndustriesWidgets { - WID_DPI_MATRIX_WIDGET, ///< Matrix of the industries. - WID_DPI_SCROLLBAR, ///< Scrollbar of the matrix. - WID_DPI_INFOPANEL, ///< Info panel about the industry. - WID_DPI_DISPLAY_WIDGET, ///< Display chain button. - WID_DPI_FUND_WIDGET, ///< Fund button. + WID_DPI_SCENARIO_EDITOR_PANE, ///< Pane containing SE-only widgets. + WID_DPI_REMOVE_ALL_INDUSTRIES_WIDGET, ///< Remove all industries button. + WID_DPI_CREATE_RANDOM_INDUSTRIES_WIDGET, ///< Create random industries button. + WID_DPI_MATRIX_WIDGET, ///< Matrix of the industries. + WID_DPI_SCROLLBAR, ///< Scrollbar of the matrix. + WID_DPI_INFOPANEL, ///< Info panel about the industry. + WID_DPI_DISPLAY_WIDGET, ///< Display chain button. + WID_DPI_FUND_WIDGET, ///< Fund button. }; /** Widgets of the #IndustryViewWindow class. */ From 64e2d6b6720ae16ba17a6b730e0319d267e07756 Mon Sep 17 00:00:00 2001 From: translators Date: Thu, 11 Feb 2021 19:01:10 +0000 Subject: [PATCH 022/122] Update: Translations from eints korean: 7 changes by telk5093 russian: 11 changes by Ln-Wolf finnish: 7 changes by hpiirai --- src/lang/finnish.txt | 8 +++++++- src/lang/korean.txt | 8 +++++++- src/lang/russian.txt | 16 +++++++++++----- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/lang/finnish.txt b/src/lang/finnish.txt index d5f5dab40b..3fc54c1d49 100644 --- a/src/lang/finnish.txt +++ b/src/lang/finnish.txt @@ -2605,12 +2605,18 @@ STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM :{BLACK}Sattuman # Fund new industry window STR_FUND_INDUSTRY_CAPTION :{WHITE}Rakenna uutta teollisuutta STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}Valitse tehdas listasta -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :Monta satunnaista teollisuusaluetta +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :{BLACK}Luo satunnaisia tuotantolaitoksia STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Sijoita teollisuuslaitokset satunnaisesti +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION :{WHITE}Luo satunnaista teollisuutta +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY :{YELLOW}Oletko varma, että haluat luoda monta satunnaista tuotantolaitosta? STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST :{BLACK}Kustannus: {YELLOW}{CURRENCY_LONG} STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY :{BLACK}Koekaivaus STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}Rakenna STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}Perusta +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES :{BLACK}Poista kaikki tuotantolaitokset +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP :{BLACK}Poista kaikki kartalla olevat tuotantolaitokset +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION :{WHITE}Poista kaikki tuotantolaitokset +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY :{YELLOW}Oletko varma, että haluat poistaa kaikki tuotantolaitokset? # Industry cargoes window STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION :{WHITE}Teollisuusketju teollisuudelle {STRING} diff --git a/src/lang/korean.txt b/src/lang/korean.txt index 4c7a16a9d7..202a28ff2f 100644 --- a/src/lang/korean.txt +++ b/src/lang/korean.txt @@ -2606,12 +2606,18 @@ STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM :{BLACK}임의 # Fund new industry window STR_FUND_INDUSTRY_CAPTION :{WHITE}새 산업시설 건설 STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}목록에서 적당한 산업시설을 선택하세요 -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :무작위 산업시설 건설 +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :{BLACK}무작위 산업시설 건설 STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}무작위로 산업시설을 건설합니다 +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION :{WHITE}무작위 산업시설 건설 +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY :{YELLOW}정말 산업시설을 무작위로 건설하시겠습니까? STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST :{BLACK}가격: {YELLOW}{CURRENCY_LONG} STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY :{BLACK}답사 STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}건설 STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}투자 +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES :{BLACK}모든 산업시설 제거 +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP :{BLACK}현재 지도 상에 있는 모든 산업시설을 제거합니다 +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION :{WHITE}모든 산업시설 제거 +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY :{YELLOW}정말 모든 산업시설을 제거하시겠습니까? # Industry cargoes window STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION :{WHITE}{STRING}{G 0 "과" "와"} 관련된 산업시설 연계도 diff --git a/src/lang/russian.txt b/src/lang/russian.txt index 4e380c1f12..9fdbe0a493 100644 --- a/src/lang/russian.txt +++ b/src/lang/russian.txt @@ -2756,8 +2756,8 @@ STR_FOUND_TOWN_NEW_TOWN_BUTTON :{BLACK}Новы STR_FOUND_TOWN_NEW_TOWN_TOOLTIP :{BLACK}Построить новый город. Shift+щелчок - оценка стоимости основания. STR_FOUND_TOWN_RANDOM_TOWN_BUTTON :{BLACK}Случайный город STR_FOUND_TOWN_RANDOM_TOWN_TOOLTIP :{BLACK}Создать город в случайном месте -STR_FOUND_TOWN_MANY_RANDOM_TOWNS :{BLACK}Много случайных городов -STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP :{BLACK}Случайно разместить на карте города +STR_FOUND_TOWN_MANY_RANDOM_TOWNS :{BLACK}Множество различных городов +STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP :{BLACK}Разместить на карте различные города случайным образом STR_FOUND_TOWN_NAME_TITLE :{YELLOW}Название города: STR_FOUND_TOWN_NAME_EDITOR_TITLE :{BLACK}Введите название города @@ -2784,13 +2784,19 @@ STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM :{BLACK}Случ # Fund new industry window STR_FUND_INDUSTRY_CAPTION :{WHITE}Создать новое предприятие -STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}Выберите тип производства из списка -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :Много предприятий -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Случайно разместить предприятия на карте +STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}Выберите тип предприятия из списка +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :{BLACK}Множество различных предприятий +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Разместить на карте различные предприятия случайным образом +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION :{WHITE}Множество различных предприятий +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY :{YELLOW}Разместить на карте различные предприятия случайным образом? STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST :{BLACK}Цена: {YELLOW}{CURRENCY_LONG} STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY :{BLACK}Разведать STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}Построить STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}Профинансировать +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES :{BLACK}Убрать все предприятия +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP :{BLACK}Убрать все предприятия с карты +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION :{WHITE}Убрать все предприятия +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY :{YELLOW}Убрать все предприятия с карты? # Industry cargoes window STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION :{WHITE}Цепочка грузоперевозок для {STRING.gen} From 569ce6c7b43be52911d1a56ed43b890f5cc2d765 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Thu, 11 Feb 2021 09:35:42 +0100 Subject: [PATCH 023/122] Fix 30e69c51: palette was not marked dirty when creating a new This means the code depended that the caller did this for us before MakePalette() is executed, which is neither a requirement nor a promise the code makes. --- src/video/sdl2_v.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index fd6cf6c112..e24b31db58 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -95,6 +95,8 @@ static void MakePalette() if (_sdl_palette == nullptr) usererror("SDL2: Couldn't allocate palette: %s", SDL_GetError()); } + _cur_palette.first_dirty = 0; + _cur_palette.count_dirty = 256; _local_palette = _cur_palette; UpdatePalette(); From 2bbef6b5cf86dac53461e10c5720a05c579304d3 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Thu, 11 Feb 2021 09:51:07 +0100 Subject: [PATCH 024/122] Codechange: [SDL2] Name paint function Paint(), like other drivers do This reduces confusion when reading different drivers. --- src/video/sdl2_v.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index e24b31db58..798f40a596 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -133,7 +133,7 @@ void VideoDriver_SDL::CheckPaletteAnim() this->MakeDirty(0, 0, _screen.width, _screen.height); } -static void DrawSurfaceToScreen() +static void Paint() { PerformanceMeasurer framerate(PFE_VIDEO); @@ -179,7 +179,7 @@ static void DrawSurfaceToScreen() _num_dirty_rects = 0; } -static void DrawSurfaceToScreenThread() +static void PaintThread() { /* First tell the main thread we're started */ std::unique_lock lock(*_draw_mutex); @@ -190,7 +190,7 @@ static void DrawSurfaceToScreenThread() while (_draw_continue) { /* Then just draw and wait till we stop */ - DrawSurfaceToScreen(); + Paint(); _draw_signal->wait(lock); } } @@ -834,7 +834,7 @@ void VideoDriver_SDL::LoopOnce() _draw_signal->notify_one(); } else { /* Oh, we didn't have threads, then just draw unthreaded */ - DrawSurfaceToScreen(); + Paint(); } } @@ -857,7 +857,7 @@ void VideoDriver_SDL::MainLoop() _draw_signal = new std::condition_variable_any(); _draw_continue = true; - _draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &DrawSurfaceToScreenThread); + _draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &PaintThread); /* Free the mutex if we won't be able to use it. */ if (!_draw_threaded) { From 2e1535389ac97c28ff2a4ea4fc82d057ba98aeed Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Thu, 11 Feb 2021 11:19:05 +0100 Subject: [PATCH 025/122] Codechange: [SDL2] Don't use globals if we can do with locals --- src/video/sdl2_v.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index 798f40a596..49e8b42078 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -58,10 +58,6 @@ static bool _cursor_new_in_window = false; static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS]; static int _num_dirty_rects; -/* Size of window */ -static int _window_size_w; -static int _window_size_h; - void VideoDriver_SDL::MakeDirty(int left, int top, int width, int height) { if (_num_dirty_rects < MAX_DIRTY_RECTS) { @@ -929,9 +925,11 @@ bool VideoDriver_SDL::ToggleFullscreen(bool fullscreen) std::unique_lock lock; if (_draw_mutex != nullptr) lock = std::unique_lock(*_draw_mutex); + int w, h; + /* Remember current window size */ if (fullscreen) { - SDL_GetWindowSize(_sdl_window, &_window_size_w, &_window_size_h); + SDL_GetWindowSize(_sdl_window, &w, &h); /* Find fullscreen window size */ SDL_DisplayMode dm; @@ -947,7 +945,7 @@ bool VideoDriver_SDL::ToggleFullscreen(bool fullscreen) if (ret == 0) { /* Switching resolution succeeded, set fullscreen value of window. */ _fullscreen = fullscreen; - if (!fullscreen) SDL_SetWindowSize(_sdl_window, _window_size_w, _window_size_h); + if (!fullscreen) SDL_SetWindowSize(_sdl_window, w, h); } else { DEBUG(driver, 0, "SDL_SetWindowFullscreen() failed: %s", SDL_GetError()); } From 52317bb7df6d153ec332e5403ef75cb7bc86b8f6 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Thu, 11 Feb 2021 11:19:33 +0100 Subject: [PATCH 026/122] Change: [SDL2] Remove unneeded delay of redrawing the screen In testing, I could find no reason why this statement is here. The comment is rather unclear (it states what it does, but not why it would be needed). This line of code was introduced with f4f40448, which gives no further insight on why it would be needed to have it here. As such, let's remove it and see if anyone else reports any problems with it. If so, this commit can be reverted and a more clear comment should be added what this line of code is dealing with (the WHY, not the WHAT). --- src/video/sdl2_v.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index 49e8b42078..6beeeac5c8 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -351,9 +351,6 @@ bool VideoDriver_SDL::CreateMainSurface(uint w, uint h, bool resize) _sdl_surface = _sdl_real_surface; } - /* Delay drawing for this cycle; the next cycle will redraw the whole screen */ - _num_dirty_rects = 0; - _screen.width = _sdl_surface->w; _screen.height = _sdl_surface->h; _screen.pitch = _sdl_surface->pitch / (bpp / 8); From fa0704138d2396875422960be6a3f1e7b6ab1341 Mon Sep 17 00:00:00 2001 From: translators Date: Fri, 12 Feb 2021 19:00:59 +0000 Subject: [PATCH 027/122] Update: Translations from eints spanish (mexican): 7 changes by absay dutch: 7 changes by Afoklala french: 7 changes by arikover --- src/lang/dutch.txt | 8 +++++++- src/lang/french.txt | 8 +++++++- src/lang/spanish_MX.txt | 8 +++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/lang/dutch.txt b/src/lang/dutch.txt index 8adce96d86..fc8fafc207 100644 --- a/src/lang/dutch.txt +++ b/src/lang/dutch.txt @@ -2605,12 +2605,18 @@ STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM :{BLACK}Willekeu # Fund new industry window STR_FUND_INDUSTRY_CAPTION :{WHITE}Nieuwe industrie financieren STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}Kiest de gewenste industrie in de lijst -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :Veel willekeurige industrieën +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :{BLACK}Willekeurige industrieën maken STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Bedek de kaart met willekeurig geplaatste industrieën +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION :{WHITE}Willekeurige industrieën maken +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY :{YELLOW}Weet je zeker dat je veel willekeurige industrieën wilt hebben? STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST :{BLACK}Kosten: {YELLOW}{CURRENCY_LONG} STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY :{BLACK}Onderzoeken STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}Bouw STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}Betaal +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES :{BLACK}Alle industrieën verwijderen +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP :{BLACK}Alle industrieën op de kaart verwijderen +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION :{WHITE}Alle industrieën verwijderen +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY :{YELLOW}Weet je zeker dat je alle industrieën wilt verwijderen? # Industry cargoes window STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION :{WHITE}Industrieketen voor {STRING} industrie diff --git a/src/lang/french.txt b/src/lang/french.txt index de7734ad5b..868063acb8 100644 --- a/src/lang/french.txt +++ b/src/lang/french.txt @@ -2606,12 +2606,18 @@ STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM :{BLACK}Aléatoi # Fund new industry window STR_FUND_INDUSTRY_CAPTION :{WHITE}Financer une nouvelle industrie STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}Choisir l'industrie appropriée dans cette liste -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :Beaucoup d'industries au hasard +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :{BLACK}Créer des industries au hasard STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Couvrir la carte d'industries placées au hasard +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION :{WHITE}Créer des industries au hasard +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY :{YELLOW}Êtes-vous sûr de vouloir créer beaucoup d'industries au hasard{NBSP}? STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST :{BLACK}Prix{NBSP}: {YELLOW}{CURRENCY_LONG} STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY :{BLACK}Prospecter STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}Construire STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}Fonder +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES :{BLACK}Enlever toutes les industries +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP :{BLACK}Enlève toutes les industries présentes sur la carte +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION :{WHITE}Enlever toutes les industries +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY :{YELLOW}Êtes-vous sûr de vouloir enlever toutes les industries{NBSP}? # Industry cargoes window STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION :{WHITE}Chaîne industrielle pour l'industrie «{NBSP}{STRING}{NBSP}» diff --git a/src/lang/spanish_MX.txt b/src/lang/spanish_MX.txt index 434a01acc0..e05d0d1393 100644 --- a/src/lang/spanish_MX.txt +++ b/src/lang/spanish_MX.txt @@ -2606,12 +2606,18 @@ STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM :{BLACK}Aleatori # Fund new industry window STR_FUND_INDUSTRY_CAPTION :{WHITE}Fundación de una nueva industria STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}Elegir de la lista la industria adecuada -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :Muchas industrias al azar +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :{BLACK}Crear industrias al azar STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Cubrir el mapa con industrias colocadas al azar +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION :{WHITE}Crear industrias al azar +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY :{YELLOW}¿Seguro de crear muchas industrias al azar? STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST :{BLACK}Costo: {YELLOW}{CURRENCY_LONG} STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY :{BLACK}Prospección STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}Construir STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}Fundar +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES :{BLACK}Eliminar todas las industrias +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP :{BLACK}Eliminar todas las industrias que haya en el mapa +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION :{WHITE}Eliminar todas las industrias +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY :{YELLOW}¿Seguro de eliminar todas las industrias? # Industry cargoes window STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION :{WHITE}Cadena industrial para la industria de {STRING} From 3ac43582c2a24238c0725524e7965f110451b643 Mon Sep 17 00:00:00 2001 From: "Johannes E. Krause" Date: Mon, 25 Jan 2021 17:29:09 +0100 Subject: [PATCH 028/122] Codechange: [NewGRF] Make it more explicit which parts of the TTD vehicle structure (var 80+) are not implemented --- src/newgrf_engine.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp index d67a5c118c..ee14098c28 100644 --- a/src/newgrf_engine.cpp +++ b/src/newgrf_engine.cpp @@ -761,16 +761,29 @@ static uint32 VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *object, } } - /* General vehicle properties */ + /* + * General vehicle properties + * + * Some parts of the TTD Vehicle structure are omitted for various reasons + * (see http://marcin.ttdpatch.net/sv1codec/TTD-locations.html#_VehicleArray) + */ switch (variable - 0x80) { case 0x00: return v->type + 0x10; case 0x01: return MapOldSubType(v); + case 0x02: break; // not implemented + case 0x03: break; // not implemented case 0x04: return v->index; case 0x05: return GB(v->index, 8, 8); + case 0x06: break; // not implemented + case 0x07: break; // not implemented + case 0x08: break; // not implemented + case 0x09: break; // not implemented case 0x0A: return v->current_order.MapOldOrder(); case 0x0B: return v->current_order.GetDestination(); case 0x0C: return v->GetNumOrders(); case 0x0D: return v->cur_real_order_index; + case 0x0E: break; // not implemented + case 0x0F: break; // not implemented case 0x10: case 0x11: { uint ticks; @@ -811,14 +824,31 @@ static uint32 VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *object, case 0x1D: return GB(v->y_pos, 8, 8); case 0x1E: return v->z_pos; case 0x1F: return object->info_view ? DIR_W : v->direction; + case 0x20: break; // not implemented + case 0x21: break; // not implemented + case 0x22: break; // not implemented + case 0x23: break; // not implemented + case 0x24: break; // not implemented + case 0x25: break; // not implemented + case 0x26: break; // not implemented + case 0x27: break; // not implemented case 0x28: return 0; // cur_image is a potential desyncer due to Action1 in static NewGRFs. case 0x29: return 0; // cur_image is a potential desyncer due to Action1 in static NewGRFs. + case 0x2A: break; // not implemented + case 0x2B: break; // not implemented + case 0x2C: break; // not implemented + case 0x2D: break; // not implemented + case 0x2E: break; // not implemented + case 0x2F: break; // not implemented + case 0x30: break; // not implemented + case 0x31: break; // not implemented case 0x32: return v->vehstatus; case 0x33: return 0; // non-existent high byte of vehstatus case 0x34: return v->type == VEH_AIRCRAFT ? (v->cur_speed * 10) / 128 : v->cur_speed; case 0x35: return GB(v->type == VEH_AIRCRAFT ? (v->cur_speed * 10) / 128 : v->cur_speed, 8, 8); case 0x36: return v->subspeed; case 0x37: return v->acceleration; + case 0x38: break; // not implemented case 0x39: return v->cargo_type; case 0x3A: return v->cargo_cap; case 0x3B: return GB(v->cargo_cap, 8, 8); @@ -856,13 +886,43 @@ static uint32 VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *object, case 0x58: return GB(ClampToI32(v->GetDisplayProfitLastYear()), 16, 16); case 0x59: return GB(ClampToI32(v->GetDisplayProfitLastYear()), 24, 8); case 0x5A: return v->Next() == nullptr ? INVALID_VEHICLE : v->Next()->index; + case 0x5B: break; // not implemented case 0x5C: return ClampToI32(v->value); case 0x5D: return GB(ClampToI32(v->value), 8, 24); case 0x5E: return GB(ClampToI32(v->value), 16, 16); case 0x5F: return GB(ClampToI32(v->value), 24, 8); + case 0x60: break; // not implemented + case 0x61: break; // not implemented + case 0x62: break; // vehicle specific, see below + case 0x63: break; // not implemented + case 0x64: break; // vehicle specific, see below + case 0x65: break; // vehicle specific, see below + case 0x66: break; // vehicle specific, see below + case 0x67: break; // vehicle specific, see below + case 0x68: break; // vehicle specific, see below + case 0x69: break; // vehicle specific, see below + case 0x6A: break; // not implemented + case 0x6B: break; // not implemented + case 0x6C: break; // not implemented + case 0x6D: break; // not implemented + case 0x6E: break; // not implemented + case 0x6F: break; // not implemented + case 0x70: break; // not implemented + case 0x71: break; // not implemented case 0x72: return v->cargo_subtype; + case 0x73: break; // vehicle specific, see below + case 0x74: break; // vehicle specific, see below + case 0x75: break; // vehicle specific, see below + case 0x76: break; // vehicle specific, see below + case 0x77: break; // vehicle specific, see below + case 0x78: break; // not implemented + case 0x79: break; // not implemented case 0x7A: return v->random_bits; case 0x7B: return v->waiting_triggers; + case 0x7C: break; // vehicle specific, see below + case 0x7D: break; // vehicle specific, see below + case 0x7E: break; // not implemented + case 0x7F: break; // vehicle specific, see below } /* Vehicle specific properties */ From 5a4d5f03b088a29ca33b52354369d68c6f0b04ee Mon Sep 17 00:00:00 2001 From: glx22 Date: Sun, 24 Jan 2021 18:16:33 +0100 Subject: [PATCH 029/122] Codechange: Implement a constructor for CurrencySpec --- src/currency.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/currency.h b/src/currency.h index c4c3db0249..0bad4a0a1e 100644 --- a/src/currency.h +++ b/src/currency.h @@ -11,6 +11,7 @@ #define CURRENCY_H #include "date_type.h" +#include "string_func.h" #include "strings_type.h" static const int CF_NOEURO = 0; ///< Currency never switches to the Euro (as far as known). @@ -83,6 +84,15 @@ struct CurrencySpec { */ byte symbol_pos; StringID name; + + CurrencySpec() = default; + + CurrencySpec(uint16 rate, const char *separator, Year to_euro, const char *prefix, const char *suffix, byte symbol_pos, StringID name) : rate(rate), to_euro(to_euro), symbol_pos(symbol_pos), name(name) + { + strecpy(this->separator, separator, lastof(this->separator)); + strecpy(this->prefix, prefix, lastof(this->prefix)); + strecpy(this->suffix, suffix, lastof(this->suffix)); + } }; extern CurrencySpec _currency_specs[CURRENCY_END]; From f181037bede9dd41b845a3a6e4c4ce6eaf57fe3d Mon Sep 17 00:00:00 2001 From: glx22 Date: Mon, 25 Jan 2021 19:28:23 +0100 Subject: [PATCH 030/122] Change: [Actions] Also run CI for VS2017 --- .github/workflows/ci-build.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 9833d5e512..029c0416e9 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -211,11 +211,10 @@ jobs: strategy: fail-fast: false matrix: - include: - - arch: x86 - - arch: x64 + os: [windows-latest, windows-2016] + arch: [x86, x64] - runs-on: windows-latest + runs-on: ${{ matrix.os }} steps: - name: Checkout From 348f322df1274405d37fae3ae0aeeb7ba46fa90f Mon Sep 17 00:00:00 2001 From: glx22 Date: Sat, 13 Feb 2021 18:38:32 +0100 Subject: [PATCH 031/122] Fix f1f281b31: [Win32] MinGW doesn't know timeapi.h --- src/os/windows/crashlog_win.cpp | 2 +- src/os/windows/win32.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/os/windows/crashlog_win.cpp b/src/os/windows/crashlog_win.cpp index b5916dd261..4529491bd7 100644 --- a/src/os/windows/crashlog_win.cpp +++ b/src/os/windows/crashlog_win.cpp @@ -20,8 +20,8 @@ #include "../../video/video_driver.hpp" #include +#include #include -#include #include "../../safeguards.h" diff --git a/src/os/windows/win32.cpp b/src/os/windows/win32.cpp index 8783b3762d..650413d023 100644 --- a/src/os/windows/win32.cpp +++ b/src/os/windows/win32.cpp @@ -14,11 +14,11 @@ #include "../../fileio_func.h" #include #include +#include #include #define NO_SHOBJIDL_SORTDIRECTION // Avoid multiple definition of SORT_ASCENDING #include /* SHGetFolderPath */ #include -#include #include "win32.h" #include "../../fios.h" #include "../../core/alloc_func.hpp" From d9df20d102c68cf7e2d7655b1006a868f5538d68 Mon Sep 17 00:00:00 2001 From: Charles Pigott Date: Sat, 13 Feb 2021 17:26:55 +0000 Subject: [PATCH 032/122] Change: Use a more specific error message when attempting to bulldoze your own HQ --- src/object_cmd.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp index 943d89322c..7a2ff26524 100644 --- a/src/object_cmd.cpp +++ b/src/object_cmd.cpp @@ -497,7 +497,10 @@ static CommandCost ClearTile_Object(TileIndex tile, DoCommandFlag flags) return_cmd_error(STR_ERROR_OWNED_BY); } else if ((spec->flags & OBJECT_FLAG_CANNOT_REMOVE) != 0 && (spec->flags & OBJECT_FLAG_AUTOREMOVE) == 0) { /* In the game editor or with cheats we can remove, otherwise we can't. */ - if (!_cheats.magic_bulldozer.value) return CMD_ERROR; + if (!_cheats.magic_bulldozer.value) { + if (type == OBJECT_HQ) return_cmd_error(STR_ERROR_COMPANY_HEADQUARTERS_IN); + return CMD_ERROR; + } /* Removing with the cheat costs more in TTDPatch / the specs. */ cost.MultiplyCost(25); From 36ab9c64efe8097b652d5fdee29dc8794a8a1b38 Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Mon, 18 Jan 2021 15:54:57 +0000 Subject: [PATCH 033/122] Fix: Desert/rainforest data coordinate 'y' is off by 1 when their 'x' coordinate is negative --- src/table/genland.h | 136 ++++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/src/table/genland.h b/src/table/genland.h index 53801e2f68..7bc6294ec7 100644 --- a/src/table/genland.h +++ b/src/table/genland.h @@ -10,121 +10,121 @@ #define M(x, y) {x, y} static const TileIndexDiffC _make_desert_or_rainforest_data[] = { - M(-5, -6), - M(-4, -6), - M(-3, -6), - M(-2, -6), - M(-1, -6), + M(-5, -5), + M(-4, -5), + M(-3, -5), + M(-2, -5), + M(-1, -5), M( 0, -5), M( 1, -5), M( 2, -5), M( 3, -5), M( 4, -5), M( 5, -5), - M(-5, -5), - M(-4, -5), - M(-3, -5), - M(-2, -5), - M(-1, -5), + M(-5, -4), + M(-4, -4), + M(-3, -4), + M(-2, -4), + M(-1, -4), M( 0, -4), M( 1, -4), M( 2, -4), M( 3, -4), M( 4, -4), M( 5, -4), - M(-5, -4), - M(-4, -4), - M(-3, -4), - M(-2, -4), - M(-1, -4), + M(-5, -3), + M(-4, -3), + M(-3, -3), + M(-2, -3), + M(-1, -3), M( 0, -3), M( 1, -3), M( 2, -3), M( 3, -3), M( 4, -3), M( 5, -3), - M(-5, -3), - M(-4, -3), - M(-3, -3), - M(-2, -3), - M(-1, -3), + M(-5, -2), + M(-4, -2), + M(-3, -2), + M(-2, -2), + M(-1, -2), M( 0, -2), M( 1, -2), M( 2, -2), M( 3, -2), M( 4, -2), M( 5, -2), - M(-5, -2), - M(-4, -2), - M(-3, -2), - M(-2, -2), - M(-1, -2), + M(-5, -1), + M(-4, -1), + M(-3, -1), + M(-2, -1), + M(-1, -1), M( 0, -1), M( 1, -1), M( 2, -1), M( 3, -1), M( 4, -1), M( 5, -1), - M(-5, -1), - M(-4, -1), - M(-3, -1), - M(-2, -1), - M(-1, -1), + M(-5, 0), + M(-4, 0), + M(-3, 0), + M(-2, 0), + M(-1, 0), M( 0, 0), M( 1, 0), M( 2, 0), M( 3, 0), M( 4, 0), M( 5, 0), - M(-5, 0), - M(-4, 0), - M(-3, 0), - M(-2, 0), - M(-1, 0), + M(-5, 1), + M(-4, 1), + M(-3, 1), + M(-2, 1), + M(-1, 1), M( 0, 1), M( 1, 1), M( 2, 1), M( 3, 1), M( 4, 1), M( 5, 1), - M(-5, 1), - M(-4, 1), - M(-3, 1), - M(-2, 1), - M(-1, 1), + M(-5, 2), + M(-4, 2), + M(-3, 2), + M(-2, 2), + M(-1, 2), M( 0, 2), M( 1, 2), M( 2, 2), M( 3, 2), M( 4, 2), M( 5, 2), - M(-5, 2), - M(-4, 2), - M(-3, 2), - M(-2, 2), - M(-1, 2), + M(-5, 3), + M(-4, 3), + M(-3, 3), + M(-2, 3), + M(-1, 3), M( 0, 3), M( 1, 3), M( 2, 3), M( 3, 3), M( 4, 3), M( 5, 3), - M(-5, 3), - M(-4, 3), - M(-3, 3), - M(-2, 3), - M(-1, 3), + M(-5, 4), + M(-4, 4), + M(-3, 4), + M(-2, 4), + M(-1, 4), M( 0, 4), M( 1, 4), M( 2, 4), M( 3, 4), M( 4, 4), M( 5, 4), - M(-5, 4), - M(-4, 4), - M(-3, 4), - M(-2, 4), - M(-1, 4), + M(-5, 5), + M(-4, 5), + M(-3, 5), + M(-2, 5), + M(-1, 5), M( 0, 5), M( 1, 5), M( 2, 5), @@ -132,31 +132,31 @@ static const TileIndexDiffC _make_desert_or_rainforest_data[] = { M( 4, 5), M( 5, 5), M( 6, -3), - M(-6, -4), - M(-3, 5), - M(-3, -7), - M( 6, -2), M(-6, -3), - M(-2, 5), - M(-2, -7), - M( 6, -1), + M(-3, 6), + M(-3, -6), + M( 6, -2), M(-6, -2), - M(-1, 5), - M(-1, -7), - M( 6, 0), + M(-2, 6), + M(-2, -6), + M( 6, -1), M(-6, -1), + M(-1, 6), + M(-1, -6), + M( 6, 0), + M(-6, 0), M( 0, 6), M( 0, -6), M( 6, 1), - M(-6, 0), + M(-6, 1), M( 1, 6), M( 1, -6), M( 6, 2), - M(-6, 1), + M(-6, 2), M( 2, 6), M( 2, -6), M( 6, 3), - M(-6, 2), + M(-6, 3), M( 3, 6), M( 3, -6) }; From 0f621b4956b82313923ef28233cf52919f70eca3 Mon Sep 17 00:00:00 2001 From: translators Date: Sat, 13 Feb 2021 18:59:59 +0000 Subject: [PATCH 034/122] Update: Translations from eints portuguese (brazilian): 36 changes by AKANexus --- src/lang/brazilian_portuguese.txt | 51 ++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/src/lang/brazilian_portuguese.txt b/src/lang/brazilian_portuguese.txt index 43f1fe0738..ad0c7afad6 100644 --- a/src/lang/brazilian_portuguese.txt +++ b/src/lang/brazilian_portuguese.txt @@ -740,6 +740,7 @@ STR_SMALLMAP_LEGENDA_DOCK :{TINY_FONT}{BLA STR_SMALLMAP_LEGENDA_ROUGH_LAND :{TINY_FONT}{BLACK}Terreno Irregular STR_SMALLMAP_LEGENDA_GRASS_LAND :{TINY_FONT}{BLACK}Gramado STR_SMALLMAP_LEGENDA_BARE_LAND :{TINY_FONT}{BLACK}Terreno Descoberto +STR_SMALLMAP_LEGENDA_RAINFOREST :{TINY_FONT}{BLACK}Floresta Tropical STR_SMALLMAP_LEGENDA_FIELDS :{TINY_FONT}{BLACK}Campos STR_SMALLMAP_LEGENDA_TREES :{TINY_FONT}{BLACK}Árvores STR_SMALLMAP_LEGENDA_ROCKS :{TINY_FONT}{BLACK}Rochas @@ -1607,6 +1608,7 @@ STR_CONFIG_SETTING_TOWN_CARGOGENMODE_BITCOUNT :Linear STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT :Posicionamento de árvores: {STRING} STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_HELPTEXT :Controla o nascimento aleatório de árvores durante o jogo. Isso pode afetar indústrias que dependem do crescimento de árvores, como serrarias STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_NO_SPREAD :Crescer sem espalhar {RED}(afeta serrarias) +STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_NO_GROWTH_NO_SPREAD :Não crescer, não espalhar {RED}(afeta serrarias) STR_CONFIG_SETTING_TOOLBAR_POS :Posição da barra de ferramentas principal: {STRING} STR_CONFIG_SETTING_TOOLBAR_POS_HELPTEXT :Posição horizontal da barra de ferramentas principal no topo da tela @@ -1673,6 +1675,7 @@ STR_CONFIG_SETTING_LOCALISATION_UNITS_VELOCITY_HELPTEXT :Sempre que uma STR_CONFIG_SETTING_LOCALISATION_UNITS_VELOCITY_IMPERIAL :Imperial (mph) STR_CONFIG_SETTING_LOCALISATION_UNITS_VELOCITY_METRIC :Métrico (km/h) STR_CONFIG_SETTING_LOCALISATION_UNITS_VELOCITY_SI :SI (m/s) +STR_CONFIG_SETTING_LOCALISATION_UNITS_VELOCITY_GAMEUNITS :Unidades de jogo (quadr./dia) STR_CONFIG_SETTING_LOCALISATION_UNITS_POWER :Unidade de potência veicular: {STRING} STR_CONFIG_SETTING_LOCALISATION_UNITS_POWER_HELPTEXT :Sempre que a potência de um veículo for exibida na interface de usuário, será exibida na unidade selecionada @@ -1805,12 +1808,13 @@ STR_INTRO_TRANSLATION :{BLACK}Faltam { # Quit window STR_QUIT_CAPTION :{WHITE}Sair +STR_QUIT_ARE_YOU_SURE_YOU_WANT_TO_EXIT_OPENTTD :{YELLOW}Tem a certeza que quer sair do OpenTTD? STR_QUIT_YES :{BLACK}Sim STR_QUIT_NO :{BLACK}Não # Abandon game STR_ABANDON_GAME_CAPTION :{WHITE}Abandonar jogo -STR_ABANDON_GAME_QUERY :{YELLOW}Você tem certeza que deseja abandonar este jogo? +STR_ABANDON_GAME_QUERY :{YELLOW}Você tem certeza que quer abandonar este jogo? STR_ABANDON_SCENARIO_QUERY :{YELLOW}Você tem certeza que quer abandonar este cenário? # Cheat window @@ -1968,6 +1972,7 @@ STR_NETWORK_SERVER_LIST_REFRESH :{BLACK}Atualiza STR_NETWORK_SERVER_LIST_REFRESH_TOOLTIP :{BLACK}Atualiza as informações sobre o servidor STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET :{BLACK}Procurar na internet +STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}Buscar na LAN STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}Adicionar servidor STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Adiciona um servidor à lista que será sempre verificada se existem jogos ocorrendo STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Iniciar servidor @@ -2198,6 +2203,7 @@ STR_NETWORK_SERVER_MESSAGE_GAME_REASON_NOT_ENOUGH_PLAYERS :número de joga STR_NETWORK_SERVER_MESSAGE_GAME_REASON_CONNECTING_CLIENTS :clientes conectando-se STR_NETWORK_SERVER_MESSAGE_GAME_REASON_MANUAL :manual STR_NETWORK_SERVER_MESSAGE_GAME_REASON_GAME_SCRIPT :{G=m}script do jogo +STR_NETWORK_SERVER_MESSAGE_GAME_REASON_LINK_GRAPH :aguardando atualização do gráfico de links ############ End of leave-in-this-order STR_NETWORK_MESSAGE_CLIENT_LEAVING :saindo STR_NETWORK_MESSAGE_CLIENT_JOINED :*** {STRING} entrou no jogo @@ -2326,6 +2332,7 @@ STR_JOIN_WAYPOINT_CAPTION :{WHITE}Unir pon STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT :{YELLOW}Construir um ponto de controle separado # Generic toolbar +STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE :{BLACK}Desativado pois não há nenhum veículo disponível para essa infraestrutura # Rail construction toolbar STR_RAIL_TOOLBAR_RAILROAD_CONSTRUCTION_CAPTION :Construir ferrovias @@ -2521,6 +2528,10 @@ STR_TREES_RANDOM_TYPE :{BLACK}Árvores STR_TREES_RANDOM_TYPE_TOOLTIP :{BLACK}Plantar árvores de tipo aleatório, Shift alterna entre construção/preço estimado STR_TREES_RANDOM_TREES_BUTTON :{BLACK}Plantar Aleatoriamente STR_TREES_RANDOM_TREES_TOOLTIP :{BLACK}Planta árvores aleatoriamente pelo terreno +STR_TREES_MODE_NORMAL_BUTTON :{BLACK}Normal +STR_TREES_MODE_FOREST_SM_BUTTON :{BLACK}Bosque +STR_TREES_MODE_FOREST_SM_TOOLTIP :{BLACK}Planta pequenas florestas ao arrastar pelo terreno. +STR_TREES_MODE_FOREST_LG_BUTTON :{BLACK}Floresta STR_TREES_MODE_FOREST_LG_TOOLTIP :{BLACK}Planta florestas grandes ao arrastar pelo terreno. # Land generation window (SE) @@ -2532,10 +2543,10 @@ STR_TERRAFORM_TOOLTIP_DECREASE_SIZE_OF_LAND_AREA :{BLACK}Diminui STR_TERRAFORM_TOOLTIP_GENERATE_RANDOM_LAND :{BLACK}Gera terreno aleatório STR_TERRAFORM_SE_NEW_WORLD :{BLACK}Criar novo cenário STR_TERRAFORM_RESET_LANDSCAPE :{BLACK}Limpar terreno -STR_TERRAFORM_RESET_LANDSCAPE_TOOLTIP :{BLACK}Remover todas as propriedades da companhia do mapa +STR_TERRAFORM_RESET_LANDSCAPE_TOOLTIP :{BLACK}Remove todas as propriedades da empresa do mapa STR_QUERY_RESET_LANDSCAPE_CAPTION :{WHITE}Limpar Terreno -STR_RESET_LANDSCAPE_CONFIRMATION_TEXT :{WHITE}Tem certeza que deseja remover todas as propriedades da companhia? +STR_RESET_LANDSCAPE_CONFIRMATION_TEXT :{WHITE}Tem certeza que quer remover todas as propriedades da empresa? # Town generation window (SE) STR_FOUND_TOWN_CAPTION :{WHITE}Gerar Cidades @@ -2574,10 +2585,14 @@ STR_FUND_INDUSTRY_CAPTION :{WHITE}Financia STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}Escolha a indústria apropriada STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :Várias indústrias aleatórias STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Cobrir o mapa com indústrias colocadas aleatoriamente +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION :{WHITE}Criar indústrias aleatórias +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY :{YELLOW}Você tem certeza que quer criar várias indústrias aleatoriamente? STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST :{BLACK}Custo: {YELLOW}{CURRENCY_LONG} STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY :{BLACK}Prosperir STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}Construir STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}Fundar +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES :{BLACK}Remover todas as indústrias +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION :{WHITE}Remover todas as indústrias # Industry cargoes window STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION :{WHITE}Cadeia de indústrias para a indústria: {STRING} @@ -3068,6 +3083,7 @@ STR_SIGN_LIST_MATCH_CASE_TOOLTIP :{BLACK}Ativa/De # Sign window STR_EDIT_SIGN_CAPTION :{WHITE}Editar texto da placa +STR_EDIT_SIGN_LOCATION_TOOLTIP :{BLACK}Centraliza janela no local da placa. Ctrl+Clique abre uma nova janela no local da placa STR_EDIT_SIGN_NEXT_SIGN_TOOLTIP :{BLACK}Próxima Placa STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP :{BLACK}Placa anterior @@ -3279,7 +3295,7 @@ STR_STATION_VIEW_CLOSE_AIRPORT_TOOLTIP :{BLACK}Impedir STR_WAYPOINT_VIEW_CAPTION :{WHITE}{WAYPOINT} STR_WAYPOINT_VIEW_CENTER_TOOLTIP :{BLACK}Centrar visão no local do ponto de caminho. Ctrl+Clique abre uma nova janela no local do ponto de caminho STR_WAYPOINT_VIEW_CHANGE_WAYPOINT_NAME :{BLACK}Renomear ponto de controle -STR_BUOY_VIEW_CENTER_TOOLTIP :{BLACK}Centrar visão no local da bóia. Ctrl+Clique abre uma nova janela no local da bóia +STR_BUOY_VIEW_CENTER_TOOLTIP :{BLACK}Centraliza janela no local da bóia. Ctrl+Clique abre uma nova janela no local da bóia STR_BUOY_VIEW_CHANGE_BUOY_NAME :{BLACK}Renomear bóia STR_EDIT_WAYPOINT_NAME :{WHITE}Editar nome do ponto de controle @@ -3344,6 +3360,7 @@ STR_COMPANY_VIEW_RELOCATE_HQ :{BLACK}Mover se STR_COMPANY_VIEW_RELOCATE_COMPANY_HEADQUARTERS :{BLACK}Reconstruir sede da empresa em outro local por 1% do valor da empresa. Shift+Clique mostra o preço estimado sem reconstruir a sede STR_COMPANY_VIEW_INFRASTRUCTURE_BUTTON :{BLACK}Detalhes STR_COMPANY_VIEW_INFRASTRUCTURE_TOOLTIP :{BLACK}Ver informações detalhadas de infraestrutura +STR_COMPANY_VIEW_GIVE_MONEY_TOOLTIP :{BLACK}Dá dinheiro a essa empresa STR_COMPANY_VIEW_NEW_FACE_BUTTON :{BLACK}Novo Rosto STR_COMPANY_VIEW_NEW_FACE_TOOLTIP :{BLACK}Selecionar novo rosto para o presidente @@ -3466,7 +3483,7 @@ STR_GROUP_LIVERY_TOOLTIP :{BLACK}Alterar STR_GROUP_REPLACE_PROTECTION_TOOLTIP :{BLACK}Clique para excluir esse grupo da substituição automática STR_QUERY_GROUP_DELETE_CAPTION :{WHITE}Remover grupo -STR_GROUP_DELETE_QUERY_TEXT :{WHITE}Tem certeza que deseja remover esse grupo e seus descendentes? +STR_GROUP_DELETE_QUERY_TEXT :{WHITE}Tem certeza que quer remover esse grupo e seus descendentes? STR_GROUP_ADD_SHARED_VEHICLE :Adicionar veículos compartilhados STR_GROUP_REMOVE_ALL_VEHICLES :Remover todos os veículos @@ -3531,19 +3548,19 @@ STR_BUY_VEHICLE_SHIP_BUY_VEHICLE_BUTTON :{BLACK}Comprar STR_BUY_VEHICLE_AIRCRAFT_BUY_VEHICLE_BUTTON :{BLACK}Comprar Aeronave STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON :{BLACK}Comprar e reequipar veículo -STR_BUY_VEHICLE_ROAD_VEHICLE_BUY_REFIT_VEHICLE_BUTTON :{BLACK}Comprar e Trocar Veículos +STR_BUY_VEHICLE_ROAD_VEHICLE_BUY_REFIT_VEHICLE_BUTTON :{BLACK}Comprar e Reequipar Veículo STR_BUY_VEHICLE_SHIP_BUY_REFIT_VEHICLE_BUTTON :{BLACK}Comprar e Repor Navio STR_BUY_VEHICLE_AIRCRAFT_BUY_REFIT_VEHICLE_BUTTON :{BLACK}Comprar e reequipar aeronaves -STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP :{BLACK}Compra o veículo ferroviário selecionado. Shift+Clique mostra preço estimado sem a compra -STR_BUY_VEHICLE_ROAD_VEHICLE_BUY_VEHICLE_TOOLTIP :{BLACK}Compra o veículo selecionado. Shift+Clique mostra preço estimado sem a compra +STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP :{BLACK}Compra o veículo ferroviário selecionado. Shift+Clique mostra preço estimado +STR_BUY_VEHICLE_ROAD_VEHICLE_BUY_VEHICLE_TOOLTIP :{BLACK}Compra o veículo selecionado. Shift+Clique mostra preço estimado STR_BUY_VEHICLE_SHIP_BUY_VEHICLE_TOOLTIP :{BLACK}Compra a embarcação selecionada. Shift+Clique mostra preço estimado sem a compra STR_BUY_VEHICLE_AIRCRAFT_BUY_VEHICLE_TOOLTIP :{BLACK}Compra a aeronave selecionada. Shift+Clique mostra o preço estimado sem a compra -STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Compre e troque o trem destacado. Shift+Click mostra os custos estimados sem comprar +STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Compra e troque o trem destacado. Shift+Click mostra o custo estimado STR_BUY_VEHICLE_ROAD_VEHICLE_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Compre e troque o veículo terrestre destacado. Shift+Clique mostra o custo estimado sem comprá-lo -STR_BUY_VEHICLE_SHIP_BUY_REFIT_VEHICLE_TOOLTIP :Compre e reponha o navio em destaque. Shift+Clique mostra o custo estimado sem comprar -STR_BUY_VEHICLE_AIRCRAFT_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Comprar e repor a aeronave destacada. Shift+Clique mostra o custo estimado sem comprar +STR_BUY_VEHICLE_SHIP_BUY_REFIT_VEHICLE_TOOLTIP :Compre e reequipa o navio selecionado. Shift+Clique mostra o custo estimado +STR_BUY_VEHICLE_AIRCRAFT_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Compra e reequipa a aeronave selecionada. Shift+Clique mostra o custo estimado STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON :{BLACK}Renomear STR_BUY_VEHICLE_ROAD_VEHICLE_RENAME_BUTTON :{BLACK}Renomear @@ -3629,9 +3646,9 @@ STR_DEPOT_CLONE_SHIP_DEPOT_INFO :{BLACK}Isso ir STR_DEPOT_CLONE_AIRCRAFT_INFO_HANGAR_WINDOW :{BLACK}Isso irá comprar uma cópia de uma aeronave. Clique nesse botão e depois em uma aeronave dentro ou fora do hangar. Ctrl+Clique compartilhará as ordens. Shift+Clique mostra o preço estimado sem a compra STR_DEPOT_TRAIN_LOCATION_TOOLTIP :{BLACK}Centrar visão no local de um depósito ferroviário. Ctrl+Clique abre uma nova janela no local do depósito de trem -STR_DEPOT_ROAD_VEHICLE_LOCATION_TOOLTIP :{BLACK}Centralizar visualização na localização da garagem. Ctrl+Clique abre uma nova janela de visualização -STR_DEPOT_SHIP_LOCATION_TOOLTIP :{BLACK}Centrar visão no local do depósito naval. Ctrl+Clique abre uma nova janela no local do depósito naval -STR_DEPOT_AIRCRAFT_LOCATION_TOOLTIP :{BLACK}Centrar visão no local do hangar. Ctrl+Clique abre uma nova janela no local do hangar +STR_DEPOT_ROAD_VEHICLE_LOCATION_TOOLTIP :{BLACK}Centraliza a janela na localização da garagem. Ctrl+Clique abre uma nova janela na localização da garagem. +STR_DEPOT_SHIP_LOCATION_TOOLTIP :{BLACK}Centraliza visão no local do depósito naval. Ctrl+Clique abre uma nova janela no local do depósito naval +STR_DEPOT_AIRCRAFT_LOCATION_TOOLTIP :{BLACK}Centraliza a janela no local do hangar. Ctrl+Clique abre uma nova janela no local do hangar STR_DEPOT_VEHICLE_ORDER_LIST_TRAIN_TOOLTIP :{BLACK}Obtém uma lista de todos os trens com este depósito em suas ordens STR_DEPOT_VEHICLE_ORDER_LIST_ROAD_VEHICLE_TOOLTIP :{BLACK}Obtém uma lista de todos os automóveis com esta garagem em suas ordens @@ -3721,6 +3738,9 @@ STR_REPLACE_REMOVE_WAGON_HELP :{BLACK}Faz auto # Vehicle view STR_VEHICLE_VIEW_CAPTION :{WHITE}{VEHICLE} +STR_VEHICLE_VIEW_ROAD_VEHICLE_CENTER_TOOLTIP :{BLACK}Centraliza a janela na localização do veículo. Clique duplo para seguir o veículo. Ctrl+Clique abre uma nova janela na localização do veículo. +STR_VEHICLE_VIEW_SHIP_CENTER_TOOLTIP :{BLACK}Centraliza a janela na localização do navio. Clique duplo para seguir o navio. Ctrl+Clique para abrir uma nova janela na localização do navio. +STR_VEHICLE_VIEW_AIRCRAFT_CENTER_TOOLTIP :{BLACK}Centraliza a janela na localização da aeronave. Clique duplo para seguir a aeronave. Ctrl+Clique abre uma nova janela na localização da aeronave. STR_VEHICLE_VIEW_TRAIN_SEND_TO_DEPOT_TOOLTIP :{BLACK}Enviar trem para o depósito STR_VEHICLE_VIEW_ROAD_VEHICLE_SEND_TO_DEPOT_TOOLTIP :{BLACK}Enviar veículo para a garagem. CTRL+clique irá apenas reparar @@ -4333,7 +4353,7 @@ STR_ERROR_INDUSTRY_TOO_CLOSE :{WHITE}... muit STR_ERROR_MUST_FOUND_TOWN_FIRST :{WHITE}... deve construir uma cidade antes STR_ERROR_ONLY_ONE_ALLOWED_PER_TOWN :{WHITE}... só é permitido uma por cidade STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS_WITH_POPULATION_OF_1200 :{WHITE}... só pode ser construído em cidades com pelo menos 1200 habitantes -STR_ERROR_CAN_ONLY_BE_BUILT_IN_RAINFOREST :{WHITE}... só pode ser construído em zonas florestais +STR_ERROR_CAN_ONLY_BE_BUILT_IN_RAINFOREST :{WHITE}... só pode ser construído em florestas tropicais STR_ERROR_CAN_ONLY_BE_BUILT_IN_DESERT :{WHITE}... só pode ser construído em áreas desérticas STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS :{WHITE}... só se pode ser construído em cidades (substituindo casas) STR_ERROR_CAN_ONLY_BE_BUILT_NEAR_TOWN_CENTER :{WHITE}... só pode ser construído próximo aos centros de cidades @@ -4430,6 +4450,7 @@ STR_ERROR_DEPOT_WRONG_DEPOT_TYPE :Tipo de depósi STR_ERROR_TRAIN_TOO_LONG_AFTER_REPLACEMENT :{WHITE}{VEHICLE} fica grande demais para ser substituído STR_ERROR_AUTOREPLACE_NOTHING_TO_DO :{WHITE}Regras de autosubstituição/renovação não estão ativadas STR_ERROR_AUTOREPLACE_MONEY_LIMIT :(limite de dinheiro) +STR_ERROR_AUTOREPLACE_INCOMPATIBLE_REFIT :{WHITE}Novo veículo não pode ser reequipado na ordem {NUM} # Rail construction errors STR_ERROR_IMPOSSIBLE_TRACK_COMBINATION :{WHITE}Impossível combinação de linhas From 9c9292949fa4d699c58d40abcac563a86df239dd Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Mon, 14 Dec 2020 00:11:39 +0100 Subject: [PATCH 035/122] Codechange: Don't use cpp_offsetof in the TTO/TTD savegame loader. Many of the member variables that are used in the oldloader are inside types that are not standard layout types. Using pointer arithmetics to determine addresses of members inside types that are not standard layout is generally undefined behaviour. If we'd use C++17, it is conditionally supported, which means each compiler may or may not support it. And even then using it for individual array elements is syntactically not supported the the standard offsetof function. --- src/saveload/oldloader.cpp | 12 +++++------- src/saveload/oldloader.h | 17 +++++++++-------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/saveload/oldloader.cpp b/src/saveload/oldloader.cpp index 42970c9a2f..6bc07ad27a 100644 --- a/src/saveload/oldloader.cpp +++ b/src/saveload/oldloader.cpp @@ -108,8 +108,6 @@ byte ReadByte(LoadgameState *ls) */ bool LoadChunk(LoadgameState *ls, void *base, const OldChunks *chunks) { - byte *base_ptr = (byte*)base; - for (const OldChunks *chunk = chunks; chunk->type != OC_END; chunk++) { if (((chunk->type & OC_TTD) && _savegame_type == SGT_TTO) || ((chunk->type & OC_TTO) && _savegame_type != SGT_TTO)) { @@ -134,8 +132,8 @@ bool LoadChunk(LoadgameState *ls, void *base, const OldChunks *chunks) break; case OC_ASSERT: - DEBUG(oldloader, 4, "Assert point: 0x%X / 0x%X", ls->total_read, chunk->offset + _bump_assert_value); - if (ls->total_read != chunk->offset + _bump_assert_value) throw std::exception(); + DEBUG(oldloader, 4, "Assert point: 0x%X / 0x%X", ls->total_read, (uint)(size_t)chunk->ptr + _bump_assert_value); + if (ls->total_read != (size_t)chunk->ptr + _bump_assert_value) throw std::exception(); default: break; } } else { @@ -153,10 +151,10 @@ bool LoadChunk(LoadgameState *ls, void *base, const OldChunks *chunks) } /* When both pointers are nullptr, we are just skipping data */ - if (base_ptr == nullptr && chunk->ptr == nullptr) continue; + if (base == nullptr && chunk->ptr == nullptr) continue; - /* Writing to the var: bits 8 to 15 have the VAR type */ - if (chunk->ptr == nullptr) ptr = base_ptr + chunk->offset; + /* Chunk refers to a struct member, get address in base. */ + if (chunk->ptr == nullptr) ptr = (byte *)chunk->offset(base); /* Write the data */ switch (GetOldChunkVarType(chunk->type)) { diff --git a/src/saveload/oldloader.h b/src/saveload/oldloader.h index 1600a9ac22..6879192ecd 100644 --- a/src/saveload/oldloader.h +++ b/src/saveload/oldloader.h @@ -82,13 +82,14 @@ enum OldChunkType { DECLARE_ENUM_AS_BIT_SET(OldChunkType) typedef bool OldChunkProc(LoadgameState *ls, int num); +typedef void *OffsetProc(void *base); struct OldChunks { OldChunkType type; ///< Type of field uint32 amount; ///< Amount of fields - void *ptr; ///< Pointer where to save the data (may only be set if offset is 0) - uint offset; ///< Offset from basepointer (may only be set if ptr is nullptr) + void *ptr; ///< Pointer where to save the data (takes precedence over #offset) + OffsetProc *offset; ///< Pointer to function that returns the actual memory address of a member (ignored if #ptr is not nullptr) OldChunkProc *proc; ///< Pointer to function that is called with OC_CHUNK }; @@ -123,12 +124,12 @@ static inline uint32 ReadUint32(LoadgameState *ls) * - OCL_CHUNK: load another proc to load a part of the savegame, 'amount' times * - OCL_ASSERT: to check if we are really at the place we expect to be.. because old savegames are too binary to be sure ;) */ -#define OCL_SVAR(type, base, offset) { type, 1, nullptr, (uint)cpp_offsetof(base, offset), nullptr } -#define OCL_VAR(type, amount, pointer) { type, amount, pointer, 0, nullptr } -#define OCL_END() { OC_END, 0, nullptr, 0, nullptr } -#define OCL_CNULL(type, amount) { OC_NULL | type, amount, nullptr, 0, nullptr } -#define OCL_CCHUNK(type, amount, proc) { OC_CHUNK | type, amount, nullptr, 0, proc } -#define OCL_ASSERT(type, size) { OC_ASSERT | type, 1, nullptr, size, nullptr } +#define OCL_SVAR(type, base, offset) { type, 1, nullptr, [] (void *b) -> void * { return std::addressof(static_cast(b)->offset); }, nullptr } +#define OCL_VAR(type, amount, pointer) { type, amount, pointer, nullptr, nullptr } +#define OCL_END() { OC_END, 0, nullptr, nullptr, nullptr } +#define OCL_CNULL(type, amount) { OC_NULL | type, amount, nullptr, nullptr, nullptr } +#define OCL_CCHUNK(type, amount, proc) { OC_CHUNK | type, amount, nullptr, nullptr, proc } +#define OCL_ASSERT(type, size) { OC_ASSERT | type, 1, (void *)(size_t)size, nullptr, nullptr } #define OCL_NULL(amount) OCL_CNULL((OldChunkType)0, amount) #define OCL_CHUNK(amount, proc) OCL_CCHUNK((OldChunkType)0, amount, proc) From 7845434270524d4c777a6711d2e17ac89eeeeb89 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Mon, 14 Dec 2020 00:14:37 +0100 Subject: [PATCH 036/122] Codechange: Don't use cpp_offsetof in the save/load code. Many of the member variables that are used in save/load are inside types that are not standard layout types. Using pointer arithmetics to determine addresses of members inside types that are not standard layout is generally undefined behaviour. If we'd use C++17, it is conditionally supported, which means each compiler may or may not support it. And even then using it for individual array elements is syntactically not supported the the standard offsetof function. Unfortunately, the trickery employed for saving linkgraph settings causes quite some clutter in the settings ini files. --- src/saveload/linkgraph_sl.cpp | 28 ++++++----- src/saveload/saveload.h | 84 +++++++++++++++------------------ src/table/company_settings.ini | 5 +- src/table/currency_settings.ini | 7 +-- src/table/gameopt_settings.ini | 13 ++--- src/table/misc_settings.ini | 15 +++--- src/table/settings.h.preamble | 84 ++++++++++++++++----------------- src/table/settings.ini | 44 ++++++++++++----- src/table/win32_settings.ini | 5 +- src/table/window_settings.ini | 5 +- 10 files changed, 155 insertions(+), 135 deletions(-) diff --git a/src/saveload/linkgraph_sl.cpp b/src/saveload/linkgraph_sl.cpp index aa3a2ff4b8..a597edfc43 100644 --- a/src/saveload/linkgraph_sl.cpp +++ b/src/saveload/linkgraph_sl.cpp @@ -53,11 +53,23 @@ const SaveLoad *GetLinkGraphJobDesc() static std::vector saveloads; static const char *prefix = "linkgraph."; + static const SaveLoad job_desc[] = { + SLE_VAR(LinkGraphJob, join_date, SLE_INT32), + SLE_VAR(LinkGraphJob, link_graph.index, SLE_UINT16), + SLE_END() + }; + + /* The member offset arithmetic below is only valid if the types in question + * are standard layout types. Otherwise, it would be undefined behaviour. */ + static_assert(std::is_standard_layout::value, "LinkGraphSettings needs to be a standard layout type"); + + /* We store the offset of each member of the #LinkGraphSettings in the + * extra data of the saveload struct. Use it together with the address + * of the settings struct inside the job to find the final memory address. */ + static SaveLoadAddrProc * const proc = [](void *b, size_t extra) -> void * { return const_cast(static_cast(reinterpret_cast(std::addressof(static_cast(b)->settings)) + extra)); }; + /* Build the SaveLoad array on first call and don't touch it later on */ if (saveloads.size() == 0) { - size_t offset_gamesettings = cpp_offsetof(GameSettings, linkgraph); - size_t offset_component = cpp_offsetof(LinkGraphJob, settings); - size_t prefixlen = strlen(prefix); int setting = 0; @@ -65,20 +77,12 @@ const SaveLoad *GetLinkGraphJobDesc() while (desc->save.cmd != SL_END) { if (desc->desc.name != nullptr && strncmp(desc->desc.name, prefix, prefixlen) == 0) { SaveLoad sl = desc->save; - char *&address = reinterpret_cast(sl.address); - address -= offset_gamesettings; - address += offset_component; + sl.address_proc = proc; saveloads.push_back(sl); } desc = GetSettingDescription(++setting); } - const SaveLoad job_desc[] = { - SLE_VAR(LinkGraphJob, join_date, SLE_INT32), - SLE_VAR(LinkGraphJob, link_graph.index, SLE_UINT16), - SLE_END() - }; - int i = 0; do { saveloads.push_back(job_desc[i++]); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index ac4658455c..7f4f0d287c 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -385,10 +385,6 @@ struct ChunkHandler { uint32 flags; ///< Flags of the chunk. @see ChunkType }; -struct NullStruct { - byte null; -}; - /** Type of reference (#SLE_REF, #SLE_CONDREF). */ enum SLRefType { REF_ORDER = 0, ///< Load/save a reference to an order. @@ -499,7 +495,7 @@ enum VarTypes { typedef uint32 VarType; /** Type of data saved. */ -enum SaveLoadTypes { +enum SaveLoadType : byte { SL_VAR = 0, ///< Save/load a variable. SL_REF = 1, ///< Save/load a reference. SL_ARR = 2, ///< Save/load an array. @@ -514,22 +510,18 @@ enum SaveLoadTypes { SL_END = 15 }; -typedef byte SaveLoadType; ///< Save/load type. @see SaveLoadTypes +typedef void *SaveLoadAddrProc(void *base, size_t extra); /** SaveLoad type struct. Do NOT use this directly but use the SLE_ macros defined just below! */ struct SaveLoad { - bool global; ///< should we load a global variable or a non-global one SaveLoadType cmd; ///< the action to take with the saved/loaded type, All types need different action VarType conv; ///< type of the variable to be saved, int uint16 length; ///< (conditional) length of the variable (eg. arrays) (max array size is 65536 elements) - SaveLoadVersion version_from; ///< save/load the variable starting from this savegame version - SaveLoadVersion version_to; ///< save/load the variable until this savegame version - /* NOTE: This element either denotes the address of the variable for a global - * variable, or the offset within a struct which is then bound to a variable - * during runtime. Decision on which one to use is controlled by the function - * that is called to save it. address: global=true, offset: global=false */ - void *address; ///< address of variable OR offset of variable in the struct (max offset is 65536) - size_t size; ///< the sizeof size. + SaveLoadVersion version_from; ///< save/load the variable starting from this savegame version + SaveLoadVersion version_to; ///< save/load the variable until this savegame version + size_t size; ///< the sizeof size. + SaveLoadAddrProc *address_proc; ///< callback proc the get the actual variable address in memory + size_t extra_data; ///< extra data for the callback proc }; /** Same as #SaveLoad but global variables are used (for better readability); */ @@ -543,9 +535,10 @@ typedef SaveLoad SaveLoadGlobVarList; * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the field. * @param to Last savegame version that has the field. + * @param extra Extra data to pass to the address callback function. * @note In general, it is better to use one of the SLE_* macros below. */ -#define SLE_GENERAL(cmd, base, variable, type, length, from, to) {false, cmd, type, length, from, to, (void*)cpp_offsetof(base, variable), cpp_sizeof(base, variable)} +#define SLE_GENERAL(cmd, base, variable, type, length, from, to, extra) {cmd, type, length, from, to, cpp_sizeof(base, variable), [] (void *b, size_t) -> void * { assert(b != nullptr); return const_cast(static_cast(std::addressof(static_cast(b)->variable))); }, extra} /** * Storage of a variable in some savegame versions. @@ -555,7 +548,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the field. * @param to Last savegame version that has the field. */ -#define SLE_CONDVAR(base, variable, type, from, to) SLE_GENERAL(SL_VAR, base, variable, type, 0, from, to) +#define SLE_CONDVAR(base, variable, type, from, to) SLE_GENERAL(SL_VAR, base, variable, type, 0, from, to, 0) /** * Storage of a reference in some savegame versions. @@ -565,7 +558,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the field. * @param to Last savegame version that has the field. */ -#define SLE_CONDREF(base, variable, type, from, to) SLE_GENERAL(SL_REF, base, variable, type, 0, from, to) +#define SLE_CONDREF(base, variable, type, from, to) SLE_GENERAL(SL_REF, base, variable, type, 0, from, to, 0) /** * Storage of an array in some savegame versions. @@ -576,7 +569,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the array. * @param to Last savegame version that has the array. */ -#define SLE_CONDARR(base, variable, type, length, from, to) SLE_GENERAL(SL_ARR, base, variable, type, length, from, to) +#define SLE_CONDARR(base, variable, type, length, from, to) SLE_GENERAL(SL_ARR, base, variable, type, length, from, to, 0) /** * Storage of a string in some savegame versions. @@ -587,7 +580,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the string. * @param to Last savegame version that has the string. */ -#define SLE_CONDSTR(base, variable, type, length, from, to) SLE_GENERAL(SL_STR, base, variable, type, length, from, to) +#define SLE_CONDSTR(base, variable, type, length, from, to) SLE_GENERAL(SL_STR, base, variable, type, length, from, to, 0) /** * Storage of a \c std::string in some savegame versions. @@ -597,7 +590,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the string. * @param to Last savegame version that has the string. */ -#define SLE_CONDSSTR(base, variable, type, from, to) SLE_GENERAL(SL_STDSTR, base, variable, type, 0, from, to) +#define SLE_CONDSSTR(base, variable, type, from, to) SLE_GENERAL(SL_STDSTR, base, variable, type, 0, from, to, 0) /** * Storage of a list in some savegame versions. @@ -607,7 +600,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the list. * @param to Last savegame version that has the list. */ -#define SLE_CONDLST(base, variable, type, from, to) SLE_GENERAL(SL_LST, base, variable, type, 0, from, to) +#define SLE_CONDLST(base, variable, type, from, to) SLE_GENERAL(SL_LST, base, variable, type, 0, from, to, 0) /** * Storage of a deque in some savegame versions. @@ -617,7 +610,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the list. * @param to Last savegame version that has the list. */ -#define SLE_CONDDEQUE(base, variable, type, from, to) SLE_GENERAL(SL_DEQUE, base, variable, type, 0, from, to) +#define SLE_CONDDEQUE(base, variable, type, from, to) SLE_GENERAL(SL_DEQUE, base, variable, type, 0, from, to, 0) /** * Storage of a variable in every version of a savegame. @@ -681,16 +674,16 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the empty space. * @param to Last savegame version that has the empty space. */ -#define SLE_CONDNULL(length, from, to) SLE_CONDARR(NullStruct, null, SLE_FILE_U8 | SLE_VAR_NULL | SLF_NOT_IN_CONFIG, length, from, to) +#define SLE_CONDNULL(length, from, to) {SL_ARR, SLE_FILE_U8 | SLE_VAR_NULL | SLF_NOT_IN_CONFIG, length, from, to, 0, nullptr, 0} /** Translate values ingame to different values in the savegame and vv. */ -#define SLE_WRITEBYTE(base, variable) SLE_GENERAL(SL_WRITEBYTE, base, variable, 0, 0, SL_MIN_VERSION, SL_MAX_VERSION) +#define SLE_WRITEBYTE(base, variable) SLE_GENERAL(SL_WRITEBYTE, base, variable, 0, 0, SL_MIN_VERSION, SL_MAX_VERSION, 0) -#define SLE_VEH_INCLUDE() {false, SL_VEH_INCLUDE, 0, 0, SL_MIN_VERSION, SL_MAX_VERSION, nullptr, 0} -#define SLE_ST_INCLUDE() {false, SL_ST_INCLUDE, 0, 0, SL_MIN_VERSION, SL_MAX_VERSION, nullptr, 0} +#define SLE_VEH_INCLUDE() {SL_VEH_INCLUDE, 0, 0, SL_MIN_VERSION, SL_MAX_VERSION, 0, [] (void *b, size_t) { return b; }, 0} +#define SLE_ST_INCLUDE() {SL_ST_INCLUDE, 0, 0, SL_MIN_VERSION, SL_MAX_VERSION, 0, [] (void *b, size_t) { return b; }, 0} /** End marker of a struct/class save or load. */ -#define SLE_END() {false, SL_END, 0, 0, SL_MIN_VERSION, SL_MIN_VERSION, nullptr, 0} +#define SLE_END() {SL_END, 0, 0, SL_MIN_VERSION, SL_MIN_VERSION, 0, nullptr, 0} /** * Storage of global simple variables, references (pointers), and arrays. @@ -699,9 +692,10 @@ typedef SaveLoad SaveLoadGlobVarList; * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the field. * @param to Last savegame version that has the field. + * @param extra Extra data to pass to the address callback function. * @note In general, it is better to use one of the SLEG_* macros below. */ -#define SLEG_GENERAL(cmd, variable, type, length, from, to) {true, cmd, type, length, from, to, (void*)&variable, sizeof(variable)} +#define SLEG_GENERAL(cmd, variable, type, length, from, to, extra) {cmd, type, length, from, to, sizeof(variable), [] (void *, size_t) -> void * { return static_cast(std::addressof(variable)); }, extra} /** * Storage of a global variable in some savegame versions. @@ -710,7 +704,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the field. * @param to Last savegame version that has the field. */ -#define SLEG_CONDVAR(variable, type, from, to) SLEG_GENERAL(SL_VAR, variable, type, 0, from, to) +#define SLEG_CONDVAR(variable, type, from, to) SLEG_GENERAL(SL_VAR, variable, type, 0, from, to, 0) /** * Storage of a global reference in some savegame versions. @@ -719,7 +713,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the field. * @param to Last savegame version that has the field. */ -#define SLEG_CONDREF(variable, type, from, to) SLEG_GENERAL(SL_REF, variable, type, 0, from, to) +#define SLEG_CONDREF(variable, type, from, to) SLEG_GENERAL(SL_REF, variable, type, 0, from, to, 0) /** * Storage of a global array in some savegame versions. @@ -729,7 +723,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the array. * @param to Last savegame version that has the array. */ -#define SLEG_CONDARR(variable, type, length, from, to) SLEG_GENERAL(SL_ARR, variable, type, length, from, to) +#define SLEG_CONDARR(variable, type, length, from, to) SLEG_GENERAL(SL_ARR, variable, type, length, from, to, 0) /** * Storage of a global string in some savegame versions. @@ -739,7 +733,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the string. * @param to Last savegame version that has the string. */ -#define SLEG_CONDSTR(variable, type, length, from, to) SLEG_GENERAL(SL_STR, variable, type, length, from, to) +#define SLEG_CONDSTR(variable, type, length, from, to) SLEG_GENERAL(SL_STR, variable, type, length, from, to, 0) /** * Storage of a global \c std::string in some savegame versions. @@ -748,7 +742,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the string. * @param to Last savegame version that has the string. */ -#define SLEG_CONDSSTR(variable, type, from, to) SLEG_GENERAL(SL_STDSTR, variable, type, 0, from, to) +#define SLEG_CONDSSTR(variable, type, from, to) SLEG_GENERAL(SL_STDSTR, variable, type, 0, from, to, 0) /** * Storage of a global list in some savegame versions. @@ -757,7 +751,7 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the list. * @param to Last savegame version that has the list. */ -#define SLEG_CONDLST(variable, type, from, to) SLEG_GENERAL(SL_LST, variable, type, 0, from, to) +#define SLEG_CONDLST(variable, type, from, to) SLEG_GENERAL(SL_LST, variable, type, 0, from, to, 0) /** * Storage of a global variable in every savegame version. @@ -807,10 +801,10 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the empty space. * @param to Last savegame version that has the empty space. */ -#define SLEG_CONDNULL(length, from, to) {true, SL_ARR, SLE_FILE_U8 | SLE_VAR_NULL | SLF_NOT_IN_CONFIG, length, from, to, (void*)nullptr} +#define SLEG_CONDNULL(length, from, to) {SL_ARR, SLE_FILE_U8 | SLE_VAR_NULL | SLF_NOT_IN_CONFIG, length, from, to, 0, nullptr, 0} /** End marker of global variables save or load. */ -#define SLEG_END() {true, SL_END, 0, 0, SL_MIN_VERSION, SL_MIN_VERSION, nullptr, 0} +#define SLEG_END() {SL_END, 0, 0, SL_MIN_VERSION, SL_MIN_VERSION, 0, nullptr, 0} /** * Checks whether the savegame is below \a major.\a minor. @@ -886,25 +880,21 @@ static inline bool IsNumericType(VarType conv) } /** - * Get the address of the variable. Which one to pick depends on the object - * pointer. If it is nullptr we are dealing with global variables so the address - * is taken. If non-null only the offset is stored in the union and we need - * to add this to the address of the object + * Get the address of the variable. Null-variables don't have an address, + * everything else has a callback function that returns the address based + * on the saveload data and the current object for non-globals. */ static inline void *GetVariableAddress(const void *object, const SaveLoad *sld) { - /* Entry is a global address. */ - if (sld->global) return sld->address; - /* Entry is a null-variable, mostly used to read old savegames etc. */ if (GetVarMemType(sld->conv) == SLE_VAR_NULL) { - assert(sld->address == nullptr); + assert(sld->address_proc == nullptr); return nullptr; } /* Everything else should be a non-null pointer. */ - assert(object != nullptr); - return const_cast((const byte *)object + (ptrdiff_t)sld->address); + assert(sld->address_proc != nullptr); + return sld->address_proc(const_cast(object), sld->extra_data); } int64 ReadValue(const void *ptr, VarType conv); diff --git a/src/table/company_settings.ini b/src/table/company_settings.ini index 8f9bee3bfe..1be7ccbc23 100644 --- a/src/table/company_settings.ini +++ b/src/table/company_settings.ini @@ -16,8 +16,8 @@ static const SettingDesc _company_settings[] = { [post-amble] }; [templates] -SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), +SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDT_END = SDT_END() [defaults] @@ -32,6 +32,7 @@ load = nullptr from = SL_MIN_VERSION to = SL_MAX_VERSION cat = SC_ADVANCED +extra = 0 diff --git a/src/table/currency_settings.ini b/src/table/currency_settings.ini index 34199501f3..f6c7c8612c 100644 --- a/src/table/currency_settings.ini +++ b/src/table/currency_settings.ini @@ -9,9 +9,9 @@ static const SettingDesc _currency_settings[] = { [post-amble] }; [templates] -SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDT_CHR = SDT_CHR($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDT_STR = SDT_STR($base, $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), +SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDT_CHR = SDT_CHR($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDT_STR = SDT_STR($base, $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDT_END = SDT_END() [defaults] @@ -26,6 +26,7 @@ load = nullptr from = SL_MIN_VERSION to = SL_MAX_VERSION cat = SC_ADVANCED +extra = 0 diff --git a/src/table/gameopt_settings.ini b/src/table/gameopt_settings.ini index 08253be93c..8e5da277f4 100644 --- a/src/table/gameopt_settings.ini +++ b/src/table/gameopt_settings.ini @@ -37,13 +37,13 @@ static const SettingDesc _gameopt_settings[] = { [post-amble] }; [templates] -SDTG_GENERAL = SDTG_GENERAL($name, $sdt_cmd, $sle_cmd, $type, $flags, $guiflags, $var, $length, $def, $min, $max, $interval, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), +SDTG_GENERAL = SDTG_GENERAL($name, $sdt_cmd, $sle_cmd, $type, $flags, $guiflags, $var, $length, $def, $min, $max, $interval, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDT_NULL = SDT_NULL($length, $from, $to), -SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDT_OMANY = SDT_OMANY($base, $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $load, $cat), -SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), +SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDT_OMANY = SDT_OMANY($base, $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $load, $cat, $extra), +SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDT_END = SDT_END() [defaults] @@ -58,6 +58,7 @@ load = nullptr from = SL_MIN_VERSION to = SL_MAX_VERSION cat = SC_ADVANCED +extra = 0 diff --git a/src/table/misc_settings.ini b/src/table/misc_settings.ini index 167f8441c2..d46ebe9bb4 100644 --- a/src/table/misc_settings.ini +++ b/src/table/misc_settings.ini @@ -13,13 +13,13 @@ static const SettingDescGlobVarList _misc_settings[] = { [post-amble] }; [templates] -SDTG_LIST = SDTG_LIST($name, $type, $length, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_MMANY = SDTG_MMANY($name, $type, $flags, $guiflags, $var, $def, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_STR = SDTG_STR($name, $type, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_SSTR = SDTG_SSTR($name, $type, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), +SDTG_LIST = SDTG_LIST($name, $type, $length, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_MMANY = SDTG_MMANY($name, $type, $flags, $guiflags, $var, $def, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_STR = SDTG_STR($name, $type, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_SSTR = SDTG_SSTR($name, $type, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDTG_END = SDTG_END() [defaults] @@ -34,6 +34,7 @@ load = nullptr from = SL_MIN_VERSION to = SL_MAX_VERSION cat = SC_ADVANCED +extra = 0 diff --git a/src/table/settings.h.preamble b/src/table/settings.h.preamble index 3d372c2452..f1facaaf09 100644 --- a/src/table/settings.h.preamble +++ b/src/table/settings.h.preamble @@ -61,29 +61,29 @@ static size_t ConvertLandscape(const char *value); /* Macros for various objects to go in the configuration file. * This section is for global variables */ -#define SDTG_GENERAL(name, sdt_cmd, sle_cmd, type, flags, guiflags, var, length, def, min, max, interval, full, str, strhelp, strval, proc, from, to, cat)\ - {NSD_GENERAL(name, def, sdt_cmd, guiflags, min, max, interval, full, str, strhelp, strval, proc, nullptr, cat), SLEG_GENERAL(sle_cmd, var, type | flags, length, from, to)} +#define SDTG_GENERAL(name, sdt_cmd, sle_cmd, type, flags, guiflags, var, length, def, min, max, interval, full, str, strhelp, strval, proc, from, to, cat, extra)\ + {NSD_GENERAL(name, def, sdt_cmd, guiflags, min, max, interval, full, str, strhelp, strval, proc, nullptr, cat), SLEG_GENERAL(sle_cmd, var, type | flags, length, from, to, extra)} -#define SDTG_VAR(name, type, flags, guiflags, var, def, min, max, interval, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(name, SDT_NUMX, SL_VAR, type, flags, guiflags, var, 0, def, min, max, interval, nullptr, str, strhelp, strval, proc, from, to, cat) +#define SDTG_VAR(name, type, flags, guiflags, var, def, min, max, interval, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(name, SDT_NUMX, SL_VAR, type, flags, guiflags, var, 0, def, min, max, interval, nullptr, str, strhelp, strval, proc, from, to, cat, extra) -#define SDTG_BOOL(name, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(name, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, var, 0, def, 0, 1, 0, nullptr, str, strhelp, strval, proc, from, to, cat) +#define SDTG_BOOL(name, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(name, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, var, 0, def, 0, 1, 0, nullptr, str, strhelp, strval, proc, from, to, cat, extra) -#define SDTG_LIST(name, type, length, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(name, SDT_INTLIST, SL_ARR, type, flags, guiflags, var, length, def, 0, 0, 0, nullptr, str, strhelp, strval, proc, from, to, cat) +#define SDTG_LIST(name, type, length, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(name, SDT_INTLIST, SL_ARR, type, flags, guiflags, var, length, def, 0, 0, 0, nullptr, str, strhelp, strval, proc, from, to, cat, extra) -#define SDTG_STR(name, type, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(name, SDT_STRING, SL_STR, type, flags, guiflags, var, sizeof(var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, from, to, cat) +#define SDTG_STR(name, type, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(name, SDT_STRING, SL_STR, type, flags, guiflags, var, sizeof(var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, from, to, cat, extra) -#define SDTG_SSTR(name, type, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(name, SDT_STDSTRING, SL_STDSTR, type, flags, guiflags, var, sizeof(var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, from, to, cat) +#define SDTG_SSTR(name, type, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(name, SDT_STDSTRING, SL_STDSTR, type, flags, guiflags, var, sizeof(var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, from, to, cat, extra) -#define SDTG_OMANY(name, type, flags, guiflags, var, def, max, full, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(name, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, var, 0, def, 0, max, 0, full, str, strhelp, strval, proc, from, to, cat) +#define SDTG_OMANY(name, type, flags, guiflags, var, def, max, full, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(name, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, var, 0, def, 0, max, 0, full, str, strhelp, strval, proc, from, to, cat, extra) -#define SDTG_MMANY(name, type, flags, guiflags, var, def, full, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(name, SDT_MANYOFMANY, SL_VAR, type, flags, guiflags, var, 0, def, 0, 0, 0, full, str, strhelp, strval, proc, from, to, cat) +#define SDTG_MMANY(name, type, flags, guiflags, var, def, full, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(name, SDT_MANYOFMANY, SL_VAR, type, flags, guiflags, var, 0, def, 0, 0, 0, full, str, strhelp, strval, proc, from, to, cat, extra) #define SDTG_NULL(length, from, to)\ {{"", nullptr, SDT_NUMX, SGF_NONE, 0, 0, 0, nullptr, STR_NULL, STR_NULL, STR_NULL, nullptr, nullptr, SC_NONE}, SLEG_NULL(length, from, to)} @@ -92,48 +92,48 @@ static size_t ConvertLandscape(const char *value); /* Macros for various objects to go in the configuration file. * This section is for structures where their various members are saved */ -#define SDT_GENERAL(name, sdt_cmd, sle_cmd, type, flags, guiflags, base, var, length, def, min, max, interval, full, str, strhelp, strval, proc, load, from, to, cat)\ - {NSD_GENERAL(name, def, sdt_cmd, guiflags, min, max, interval, full, str, strhelp, strval, proc, load, cat), SLE_GENERAL(sle_cmd, base, var, type | flags, length, from, to)} +#define SDT_GENERAL(name, sdt_cmd, sle_cmd, type, flags, guiflags, base, var, length, def, min, max, interval, full, str, strhelp, strval, proc, load, from, to, cat, extra)\ + {NSD_GENERAL(name, def, sdt_cmd, guiflags, min, max, interval, full, str, strhelp, strval, proc, load, cat), SLE_GENERAL(sle_cmd, base, var, type | flags, length, from, to, extra)} -#define SDT_VAR(base, var, type, flags, guiflags, def, min, max, interval, str, strhelp, strval, proc, from, to, cat)\ - SDT_GENERAL(#var, SDT_NUMX, SL_VAR, type, flags, guiflags, base, var, 1, def, min, max, interval, nullptr, str, strhelp, strval, proc, nullptr, from, to, cat) +#define SDT_VAR(base, var, type, flags, guiflags, def, min, max, interval, str, strhelp, strval, proc, from, to, cat, extra)\ + SDT_GENERAL(#var, SDT_NUMX, SL_VAR, type, flags, guiflags, base, var, 1, def, min, max, interval, nullptr, str, strhelp, strval, proc, nullptr, from, to, cat, extra) -#define SDT_BOOL(base, var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\ - SDT_GENERAL(#var, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, base, var, 1, def, 0, 1, 0, nullptr, str, strhelp, strval, proc, nullptr, from, to, cat) +#define SDT_BOOL(base, var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDT_GENERAL(#var, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, base, var, 1, def, 0, 1, 0, nullptr, str, strhelp, strval, proc, nullptr, from, to, cat, extra) -#define SDT_LIST(base, var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\ - SDT_GENERAL(#var, SDT_INTLIST, SL_ARR, type, flags, guiflags, base, var, lengthof(((base*)8)->var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, nullptr, from, to, cat) +#define SDT_LIST(base, var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDT_GENERAL(#var, SDT_INTLIST, SL_ARR, type, flags, guiflags, base, var, lengthof(((base*)8)->var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, nullptr, from, to, cat, extra) -#define SDT_STR(base, var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\ - SDT_GENERAL(#var, SDT_STRING, SL_STR, type, flags, guiflags, base, var, sizeof(((base*)8)->var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, nullptr, from, to, cat) +#define SDT_STR(base, var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDT_GENERAL(#var, SDT_STRING, SL_STR, type, flags, guiflags, base, var, sizeof(((base*)8)->var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, nullptr, from, to, cat, extra) -#define SDT_CHR(base, var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\ - SDT_GENERAL(#var, SDT_STRING, SL_VAR, SLE_CHAR, flags, guiflags, base, var, 1, def, 0, 0, 0, nullptr, str, strhelp, strval, proc, nullptr, from, to, cat) +#define SDT_CHR(base, var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDT_GENERAL(#var, SDT_STRING, SL_VAR, SLE_CHAR, flags, guiflags, base, var, 1, def, 0, 0, 0, nullptr, str, strhelp, strval, proc, nullptr, from, to, cat, extra) -#define SDT_OMANY(base, var, type, flags, guiflags, def, max, full, str, strhelp, strval, proc, from, to, load, cat)\ - SDT_GENERAL(#var, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, base, var, 1, def, 0, max, 0, full, str, strhelp, strval, proc, load, from, to, cat) +#define SDT_OMANY(base, var, type, flags, guiflags, def, max, full, str, strhelp, strval, proc, from, to, load, cat, extra)\ + SDT_GENERAL(#var, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, base, var, 1, def, 0, max, 0, full, str, strhelp, strval, proc, load, from, to, cat, extra) -#define SDT_MMANY(base, var, type, flags, guiflags, def, full, str, proc, strhelp, strval, from, to, cat)\ - SDT_GENERAL(#var, SDT_MANYOFMANY, SL_VAR, type, flags, guiflags, base, var, 1, def, 0, 0, 0, full, str, strhelp, strval, proc, nullptr, from, to, cat) +#define SDT_MMANY(base, var, type, flags, guiflags, def, full, str, proc, strhelp, strval, from, to, cat, extra)\ + SDT_GENERAL(#var, SDT_MANYOFMANY, SL_VAR, type, flags, guiflags, base, var, 1, def, 0, 0, 0, full, str, strhelp, strval, proc, nullptr, from, to, cat, extra) #define SDT_NULL(length, from, to)\ {{"", nullptr, SDT_NUMX, SGF_NONE, 0, 0, 0, nullptr, STR_NULL, STR_NULL, STR_NULL, nullptr, nullptr, SC_NONE}, SLE_CONDNULL(length, from, to)} -#define SDTC_VAR(var, type, flags, guiflags, def, min, max, interval, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(#var, SDT_NUMX, SL_VAR, type, flags, guiflags, _settings_client.var, 1, def, min, max, interval, nullptr, str, strhelp, strval, proc, from, to, cat) +#define SDTC_VAR(var, type, flags, guiflags, def, min, max, interval, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(#var, SDT_NUMX, SL_VAR, type, flags, guiflags, _settings_client.var, 1, def, min, max, interval, nullptr, str, strhelp, strval, proc, from, to, cat, extra) -#define SDTC_BOOL(var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(#var, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, _settings_client.var, 1, def, 0, 1, 0, nullptr, str, strhelp, strval, proc, from, to, cat) +#define SDTC_BOOL(var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(#var, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, _settings_client.var, 1, def, 0, 1, 0, nullptr, str, strhelp, strval, proc, from, to, cat, extra) -#define SDTC_LIST(var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(#var, SDT_INTLIST, SL_ARR, type, flags, guiflags, _settings_client.var, lengthof(_settings_client.var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, from, to, cat) +#define SDTC_LIST(var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(#var, SDT_INTLIST, SL_ARR, type, flags, guiflags, _settings_client.var, lengthof(_settings_client.var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, from, to, cat, extra) -#define SDTC_STR(var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(#var, SDT_STRING, SL_STR, type, flags, guiflags, _settings_client.var, sizeof(_settings_client.var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, from, to, cat) +#define SDTC_STR(var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(#var, SDT_STRING, SL_STR, type, flags, guiflags, _settings_client.var, sizeof(_settings_client.var), def, 0, 0, 0, nullptr, str, strhelp, strval, proc, from, to, cat, extra) -#define SDTC_OMANY(var, type, flags, guiflags, def, max, full, str, strhelp, strval, proc, from, to, cat)\ - SDTG_GENERAL(#var, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, _settings_client.var, 1, def, 0, max, 0, full, str, strhelp, strval, proc, from, to, cat) +#define SDTC_OMANY(var, type, flags, guiflags, def, max, full, str, strhelp, strval, proc, from, to, cat, extra)\ + SDTG_GENERAL(#var, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, _settings_client.var, 1, def, 0, max, 0, full, str, strhelp, strval, proc, from, to, cat, extra) #define SDT_END() {{nullptr, nullptr, SDT_NUMX, SGF_NONE, 0, 0, 0, nullptr, STR_NULL, STR_NULL, STR_NULL, nullptr, nullptr, SC_NONE}, SLE_END()} diff --git a/src/table/settings.ini b/src/table/settings.ini index eebb23f7e9..2e085058f2 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -62,18 +62,18 @@ const SettingDesc _settings[] = { [post-amble] }; [templates] -SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTC_BOOL = SDTC_BOOL( $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTC_LIST = SDTC_LIST( $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTC_STR = SDTC_STR( $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTC_VAR = SDTC_VAR( $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDT_OMANY = SDT_OMANY($base, $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $load, $cat), -SDT_STR = SDT_STR($base, $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), +SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTC_BOOL = SDTC_BOOL( $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTC_LIST = SDTC_LIST( $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTC_STR = SDTC_STR( $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTC_VAR = SDTC_VAR( $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDT_OMANY = SDT_OMANY($base, $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $load, $cat, $extra), +SDT_STR = SDT_STR($base, $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDT_NULL = SDT_NULL($length, $from, $to), SDT_END = SDT_END() @@ -89,6 +89,7 @@ load = nullptr from = SL_MIN_VERSION to = SL_MAX_VERSION cat = SC_ADVANCED +extra = 0 @@ -644,6 +645,7 @@ interval = 2 str = STR_CONFIG_SETTING_LINKGRAPH_INTERVAL strval = STR_JUST_COMMA strhelp = STR_CONFIG_SETTING_LINKGRAPH_INTERVAL_HELPTEXT +extra = offsetof(LinkGraphSettings, recalc_interval) [SDT_VAR] base = GameSettings @@ -657,6 +659,8 @@ interval = 1 str = STR_CONFIG_SETTING_LINKGRAPH_TIME strval = STR_JUST_COMMA strhelp = STR_CONFIG_SETTING_LINKGRAPH_TIME_HELPTEXT +extra = offsetof(LinkGraphSettings, recalc_time) + [SDT_VAR] base = GameSettings @@ -671,6 +675,8 @@ interval = 1 str = STR_CONFIG_SETTING_DISTRIBUTION_PAX strval = STR_CONFIG_SETTING_DISTRIBUTION_MANUAL strhelp = STR_CONFIG_SETTING_DISTRIBUTION_PAX_HELPTEXT +extra = offsetof(LinkGraphSettings, distribution_pax) + [SDT_VAR] base = GameSettings @@ -685,6 +691,8 @@ interval = 1 str = STR_CONFIG_SETTING_DISTRIBUTION_MAIL strval = STR_CONFIG_SETTING_DISTRIBUTION_MANUAL strhelp = STR_CONFIG_SETTING_DISTRIBUTION_MAIL_HELPTEXT +extra = offsetof(LinkGraphSettings, distribution_mail) + [SDT_VAR] base = GameSettings @@ -699,6 +707,8 @@ interval = 1 str = STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED strval = STR_CONFIG_SETTING_DISTRIBUTION_MANUAL strhelp = STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED_HELPTEXT +extra = offsetof(LinkGraphSettings, distribution_armoured) + [SDT_VAR] base = GameSettings @@ -713,6 +723,8 @@ interval = 1 str = STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT strval = STR_CONFIG_SETTING_DISTRIBUTION_MANUAL strhelp = STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT_HELPTEXT +extra = offsetof(LinkGraphSettings, distribution_default) + [SDT_VAR] base = GameSettings @@ -726,6 +738,8 @@ interval = 1 str = STR_CONFIG_SETTING_LINKGRAPH_ACCURACY strval = STR_JUST_COMMA strhelp = STR_CONFIG_SETTING_LINKGRAPH_ACCURACY_HELPTEXT +extra = offsetof(LinkGraphSettings, accuracy) + [SDT_VAR] base = GameSettings @@ -739,6 +753,8 @@ interval = 5 str = STR_CONFIG_SETTING_DEMAND_DISTANCE strval = STR_CONFIG_SETTING_PERCENTAGE strhelp = STR_CONFIG_SETTING_DEMAND_DISTANCE_HELPTEXT +extra = offsetof(LinkGraphSettings, demand_distance) + [SDT_VAR] base = GameSettings @@ -752,6 +768,8 @@ interval = 5 str = STR_CONFIG_SETTING_DEMAND_SIZE strval = STR_CONFIG_SETTING_PERCENTAGE strhelp = STR_CONFIG_SETTING_DEMAND_SIZE_HELPTEXT +extra = offsetof(LinkGraphSettings, demand_size) + [SDT_VAR] base = GameSettings @@ -765,6 +783,8 @@ interval = 5 str = STR_CONFIG_SETTING_SHORT_PATH_SATURATION strval = STR_CONFIG_SETTING_PERCENTAGE strhelp = STR_CONFIG_SETTING_SHORT_PATH_SATURATION_HELPTEXT +extra = offsetof(LinkGraphSettings, short_path_saturation) + ; Vehicles diff --git a/src/table/win32_settings.ini b/src/table/win32_settings.ini index c7915e32bd..d1cc72dd77 100644 --- a/src/table/win32_settings.ini +++ b/src/table/win32_settings.ini @@ -15,8 +15,8 @@ static const SettingDescGlobVarList _win32_settings[] = { }; #endif /* _WIN32 */ [templates] -SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), +SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDTG_END = SDTG_END() [defaults] @@ -31,6 +31,7 @@ load = nullptr from = SL_MIN_VERSION to = SL_MAX_VERSION cat = SC_ADVANCED +extra = 0 diff --git a/src/table/window_settings.ini b/src/table/window_settings.ini index fc0c57b39e..524e02fdfa 100644 --- a/src/table/window_settings.ini +++ b/src/table/window_settings.ini @@ -10,8 +10,8 @@ static const SettingDesc _window_settings[] = { [post-amble] }; [templates] -SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), -SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), +SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDT_END = SDT_END() [defaults] @@ -27,6 +27,7 @@ load = nullptr from = SL_MIN_VERSION to = SL_MAX_VERSION cat = SC_ADVANCED +extra = 0 From 84636fc2af4dfc77b43479909273ebadb4f9b7cb Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Mon, 14 Dec 2020 00:22:04 +0100 Subject: [PATCH 037/122] Codechange: Remove all remaining uses of cpp_offset. --- src/newgrf_debug_gui.cpp | 24 +++++++++++++----------- src/stdafx.h | 7 +------ src/table/newgrf_debug_data.h | 4 ++-- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp index da3ddff0aa..f82f4d93b3 100644 --- a/src/newgrf_debug_gui.cpp +++ b/src/newgrf_debug_gui.cpp @@ -79,12 +79,14 @@ enum NIType { NIT_CARGO, ///< The property is a cargo }; +typedef const void *NIOffsetProc(const void *b); + /** Representation of the data from a NewGRF property. */ struct NIProperty { - const char *name; ///< A (human readable) name for the property - ptrdiff_t offset; ///< Offset of the variable in the class - byte read_size; ///< Number of bytes (i.e. byte, word, dword etc) - byte prop; ///< The number of the property + const char *name; ///< A (human readable) name for the property + NIOffsetProc *offset_proc; ///< Callback proc to get the actual variable address in memory + byte read_size; ///< Number of bytes (i.e. byte, word, dword etc) + byte prop; ///< The number of the property byte type; }; @@ -94,11 +96,11 @@ struct NIProperty { * information on when they actually apply. */ struct NICallback { - const char *name; ///< The human readable name of the callback - ptrdiff_t offset; ///< Offset of the variable in the class - byte read_size; ///< The number of bytes (i.e. byte, word, dword etc) to read - byte cb_bit; ///< The bit that needs to be set for this callback to be enabled - uint16 cb_id; ///< The number of the callback + const char *name; ///< The human readable name of the callback + NIOffsetProc *offset_proc; ///< Callback proc to get the actual variable address in memory + byte read_size; ///< The number of bytes (i.e. byte, word, dword etc) to read + byte cb_bit; ///< The bit that needs to be set for this callback to be enabled + uint16 cb_id; ///< The number of the callback }; /** Mask to show no bit needs to be enabled for the callback. */ static const int CBM_NO_BIT = UINT8_MAX; @@ -491,7 +493,7 @@ struct NewGRFInspectWindow : Window { if (nif->properties != nullptr) { this->DrawString(r, i++, "Properties:"); for (const NIProperty *nip = nif->properties; nip->name != nullptr; nip++) { - const void *ptr = (const byte *)base + nip->offset; + const void *ptr = nip->offset_proc(base); uint value; switch (nip->read_size) { case 1: value = *(const uint8 *)ptr; break; @@ -525,7 +527,7 @@ struct NewGRFInspectWindow : Window { this->DrawString(r, i++, "Callbacks:"); for (const NICallback *nic = nif->callbacks; nic->name != nullptr; nic++) { if (nic->cb_bit != CBM_NO_BIT) { - const void *ptr = (const byte *)base_spec + nic->offset; + const void *ptr = nic->offset_proc(base_spec); uint value; switch (nic->read_size) { case 1: value = *(const uint8 *)ptr; break; diff --git a/src/stdafx.h b/src/stdafx.h index e5cb9205ff..7ee3ce2041 100644 --- a/src/stdafx.h +++ b/src/stdafx.h @@ -392,18 +392,13 @@ static_assert(SIZE_MAX >= UINT32_MAX); */ #define lastof(x) (&x[lengthof(x) - 1]) -#define cpp_offsetof(s, m) (((size_t)&reinterpret_cast((((s*)(char*)8)->m))) - 8) -#if !defined(offsetof) -# define offsetof(s, m) cpp_offsetof(s, m) -#endif /* offsetof */ - /** * Gets the size of a variable within a class. * @param base The class the variable is in. * @param variable The variable to get the size of. * @return the size of the variable */ -#define cpp_sizeof(base, variable) (sizeof(((base*)8)->variable)) +#define cpp_sizeof(base, variable) (sizeof(std::declval().variable)) /** * Gets the length of an array variable within a class. diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index e31d093385..e35b2eecbf 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -12,11 +12,11 @@ #include "../newgrf_roadtype.h" /* Helper for filling property tables */ -#define NIP(prop, base, variable, type, name) { name, (ptrdiff_t)cpp_offsetof(base, variable), cpp_sizeof(base, variable), prop, type } +#define NIP(prop, base, variable, type, name) { name, [] (const void *b) -> const void * { return std::addressof(static_cast(b)->variable); }, cpp_sizeof(base, variable), prop, type } #define NIP_END() { nullptr, 0, 0, 0, 0 } /* Helper for filling callback tables */ -#define NIC(cb_id, base, variable, bit) { #cb_id, (ptrdiff_t)cpp_offsetof(base, variable), cpp_sizeof(base, variable), bit, cb_id } +#define NIC(cb_id, base, variable, bit) { #cb_id, [] (const void *b) -> const void * { return std::addressof(static_cast(b)->variable); }, cpp_sizeof(base, variable), bit, cb_id } #define NIC_END() { nullptr, 0, 0, 0, 0 } /* Helper for filling variable tables */ From 2b0200d4297a67916cf3b955adc474e88e327cf0 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Mon, 4 Jan 2021 15:20:34 +0100 Subject: [PATCH 038/122] Codechange: OS-specific data for font search is not used outside of searching. --- src/fontcache.h | 2 +- src/fontdetection.cpp | 4 +--- src/strings.cpp | 4 ---- src/textfile_gui.cpp | 1 - 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/fontcache.h b/src/fontcache.h index 716b3e7d77..4c44876da1 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -217,7 +217,7 @@ struct FreeTypeSubSetting { uint size; ///< The (requested) size of the font. bool aa; ///< Whether to do anti aliasing or not. - const void *os_handle = nullptr; ///< Optional native OS font info. + const void *os_handle = nullptr; ///< Optional native OS font info. Only valid during font search. }; /** Settings for the freetype fonts. */ diff --git a/src/fontdetection.cpp b/src/fontdetection.cpp index 570e1aa068..dfb27a35de 100644 --- a/src/fontdetection.cpp +++ b/src/fontdetection.cpp @@ -344,9 +344,7 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT const char *english_name = font_name; #endif /* WITH_FREETYPE */ - PLOGFONT os_data = MallocT(1); - *os_data = logfont->elfLogFont; - info->callback->SetFontNames(info->settings, font_name, os_data); + info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont); if (info->callback->FindMissingGlyphs()) return 1; DEBUG(freetype, 1, "Fallback font: %s (%s)", font_name, english_name); return 0; // stop enumerating diff --git a/src/strings.cpp b/src/strings.cpp index fa039fdb97..1d6fca8b6f 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2078,7 +2078,6 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher { strecpy(settings->medium.font, font_name, lastof(settings->medium.font)); strecpy(settings->large.font, font_name, lastof(settings->large.font)); - free(settings->medium.os_handle); // Only free one, they are all the same pointer. settings->small.os_handle = os_data; settings->medium.os_handle = os_data; settings->large.os_handle = os_data; @@ -2116,9 +2115,6 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher) bad_font = !SetFallbackFont(&_freetype, _langpack.langpack->isocode, _langpack.langpack->winlangid, searcher); - free(_freetype.mono.os_handle); - free(_freetype.medium.os_handle); - memcpy(&_freetype, &backup, sizeof(backup)); if (!bad_font) { diff --git a/src/textfile_gui.cpp b/src/textfile_gui.cpp index b83e38187f..fd544f5fc4 100644 --- a/src/textfile_gui.cpp +++ b/src/textfile_gui.cpp @@ -196,7 +196,6 @@ void TextfileWindow::SetupScrollbars() { #if defined(WITH_FREETYPE) || defined(_WIN32) strecpy(settings->mono.font, font_name, lastof(settings->mono.font)); - free(settings->mono.os_handle); settings->mono.os_handle = os_data; #endif } From 5ad16409847a17534389054a1b7f5fb83703fb4d Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Wed, 6 Jan 2021 22:56:44 +0100 Subject: [PATCH 039/122] Codechange: Make OS font handle of the FontCache read-only. --- src/fontcache.cpp | 2 +- src/fontcache.h | 2 +- src/os/windows/string_uniscribe.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 0b2764aabf..9cdc0b9755 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -756,7 +756,7 @@ public: virtual GlyphID MapCharToGlyph(WChar key); virtual const char *GetFontName() { return WIDE_TO_MB(this->logfont.lfFaceName); } virtual bool IsBuiltInFont() { return false; } - virtual void *GetOSHandle() { return &this->logfont; } + virtual const void *GetOSHandle() { return &this->logfont; } }; diff --git a/src/fontcache.h b/src/fontcache.h index 4c44876da1..fac08a3537 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -127,7 +127,7 @@ public: * Get the native OS font handle, if there is one. * @return Opaque OS font handle. */ - virtual void *GetOSHandle() + virtual const void *GetOSHandle() { return nullptr; } diff --git a/src/os/windows/string_uniscribe.cpp b/src/os/windows/string_uniscribe.cpp index ad37548662..6c0a9366e9 100644 --- a/src/os/windows/string_uniscribe.cpp +++ b/src/os/windows/string_uniscribe.cpp @@ -144,7 +144,7 @@ void UniscribeResetScriptCache(FontSize size) /** Load the matching native Windows font. */ static HFONT HFontFromFont(Font *font) { - if (font->fc->GetOSHandle() != nullptr) return CreateFontIndirect((PLOGFONT)font->fc->GetOSHandle()); + if (font->fc->GetOSHandle() != nullptr) return CreateFontIndirect((const PLOGFONT)font->fc->GetOSHandle()); LOGFONT logfont; ZeroMemory(&logfont, sizeof(LOGFONT)); From c6af8f16f63d878371d1a823c12fbf21423505b5 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 13 Feb 2021 17:34:22 +0100 Subject: [PATCH 040/122] Codechange: [Win32] Move Win32-specific font code to a seperate file. --- src/CMakeLists.txt | 1 + src/fontcache.cpp | 393 +------------------- src/fontcache.h | 7 +- src/fontcache_internal.h | 76 ++++ src/fontdetection.cpp | 361 +----------------- src/fontdetection.h | 5 +- src/os/windows/CMakeLists.txt | 2 + src/os/windows/font_win32.cpp | 675 ++++++++++++++++++++++++++++++++++ src/os/windows/font_win32.h | 42 +++ 9 files changed, 818 insertions(+), 744 deletions(-) create mode 100644 src/fontcache_internal.h create mode 100644 src/os/windows/font_win32.cpp create mode 100644 src/os/windows/font_win32.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bbe66b7645..d451ae8da6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -155,6 +155,7 @@ add_files( fios_gui.cpp fontcache.cpp fontcache.h + fontcache_internal.h fontdetection.cpp fontdetection.h framerate_gui.cpp diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 9cdc0b9755..6cc25154f8 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -9,6 +9,7 @@ #include "stdafx.h" #include "fontcache.h" +#include "fontcache_internal.h" #include "fontdetection.h" #include "blitter/factory.hpp" #include "core/math_func.hpp" @@ -31,6 +32,8 @@ static const int ASCII_LETTERSTART = 32; ///< First printable ASCII letter. static const int _default_font_height[FS_END] = {10, 6, 18, 10}; static const int _default_font_ascender[FS_END] = { 8, 5, 15, 8}; +FreeTypeSettings _freetype; + /** * Create a new font cache. * @param fs The size of the font. @@ -52,6 +55,11 @@ FontCache::~FontCache() Layouter::ResetFontCache(this->fs); } +int FontCache::GetDefaultFontHeight(FontSize fs) +{ + return _default_font_height[fs]; +} + /** * Get height of a character for a given font size. @@ -199,65 +207,6 @@ bool SpriteFontCache::GetDrawGlyphShadow() #if defined(WITH_FREETYPE) || defined(_WIN32) -FreeTypeSettings _freetype; - -static const int MAX_FONT_SIZE = 72; ///< Maximum font size. - -static const byte FACE_COLOUR = 1; -static const byte SHADOW_COLOUR = 2; - -/** Font cache for fonts that are based on a TrueType font. */ -class TrueTypeFontCache : public FontCache { -protected: - int req_size; ///< Requested font size. - int used_size; ///< Used font size. - - typedef SmallMap > FontTable; ///< Table with font table cache - FontTable font_tables; ///< Cached font tables. - - /** Container for information about a glyph. */ - struct GlyphEntry { - Sprite *sprite; ///< The loaded sprite. - byte width; ///< The width of the glyph. - bool duplicate; ///< Whether this glyph entry is a duplicate, i.e. may this be freed? - }; - - /** - * The glyph cache. This is structured to reduce memory consumption. - * 1) There is a 'segment' table for each font size. - * 2) Each segment table is a discrete block of characters. - * 3) Each block contains 256 (aligned) characters sequential characters. - * - * The cache is accessed in the following way: - * For character 0x0041 ('A'): glyph_to_sprite[0x00][0x41] - * For character 0x20AC (Euro): glyph_to_sprite[0x20][0xAC] - * - * Currently only 256 segments are allocated, "limiting" us to 65536 characters. - * This can be simply changed in the two functions Get & SetGlyphPtr. - */ - GlyphEntry **glyph_to_sprite; - - GlyphEntry *GetGlyphPtr(GlyphID key); - void SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool duplicate = false); - - virtual const void *InternalGetFontTable(uint32 tag, size_t &length) = 0; - virtual const Sprite *InternalGetGlyph(GlyphID key, bool aa) = 0; - -public: - TrueTypeFontCache(FontSize fs, int pixels); - virtual ~TrueTypeFontCache(); - virtual int GetFontSize() const { return this->used_size; } - virtual SpriteID GetUnicodeGlyph(WChar key) { return this->parent->GetUnicodeGlyph(key); } - virtual void SetUnicodeGlyph(WChar key, SpriteID sprite) { this->parent->SetUnicodeGlyph(key, sprite); } - virtual void InitializeUnicodeGlyphMap() { this->parent->InitializeUnicodeGlyphMap(); } - virtual const Sprite *GetGlyph(GlyphID key); - virtual const void *GetFontTable(uint32 tag, size_t &length); - virtual void ClearFontCache(); - virtual uint GetGlyphWidth(GlyphID key); - virtual bool GetDrawGlyphShadow(); - virtual bool IsBuiltInFont() { return false; } -}; - /** * Create a new TrueTypeFontCache. * @param fs The font size that is going to be cached. @@ -329,7 +278,7 @@ void TrueTypeFontCache::SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool d this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].duplicate = duplicate; } -static void *AllocateFont(size_t size) +void *AllocateFont(size_t size) { return MallocT(size); } @@ -731,330 +680,7 @@ const void *FreeTypeFontCache::InternalGetFontTable(uint32 tag, size_t &length) #elif defined(_WIN32) -#include "os/windows/win32.h" -#ifndef ANTIALIASED_QUALITY -#define ANTIALIASED_QUALITY 4 -#endif - -/** Font cache for fonts that are based on a Win32 font. */ -class Win32FontCache : public TrueTypeFontCache { -private: - LOGFONT logfont; ///< Logical font information for selecting the font face. - HFONT font = nullptr; ///< The font face associated with this font. - HDC dc = nullptr; ///< Cached GDI device context. - HGDIOBJ old_font; ///< Old font selected into the GDI context. - SIZE glyph_size; ///< Maximum size of regular glyphs. - - void SetFontSize(FontSize fs, int pixels); - virtual const void *InternalGetFontTable(uint32 tag, size_t &length); - virtual const Sprite *InternalGetGlyph(GlyphID key, bool aa); - -public: - Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels); - ~Win32FontCache(); - virtual void ClearFontCache(); - virtual GlyphID MapCharToGlyph(WChar key); - virtual const char *GetFontName() { return WIDE_TO_MB(this->logfont.lfFaceName); } - virtual bool IsBuiltInFont() { return false; } - virtual const void *GetOSHandle() { return &this->logfont; } -}; - - -/** - * Create a new Win32FontCache. - * @param fs The font size that is going to be cached. - * @param logfont The font that has to be loaded. - * @param pixels The number of pixels this font should be high. - */ -Win32FontCache::Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels) : TrueTypeFontCache(fs, pixels), logfont(logfont) -{ - this->dc = CreateCompatibleDC(nullptr); - this->SetFontSize(fs, pixels); -} - -Win32FontCache::~Win32FontCache() -{ - this->ClearFontCache(); - DeleteDC(this->dc); - DeleteObject(this->font); -} -void Win32FontCache::SetFontSize(FontSize fs, int pixels) -{ - if (pixels == 0) { - /* Try to determine a good height based on the minimal height recommended by the font. */ - int scaled_height = ScaleFontTrad(_default_font_height[this->fs]); - pixels = scaled_height; - - HFONT temp = CreateFontIndirect(&this->logfont); - if (temp != nullptr) { - HGDIOBJ old = SelectObject(this->dc, temp); - - UINT size = GetOutlineTextMetrics(this->dc, 0, nullptr); - LPOUTLINETEXTMETRIC otm = (LPOUTLINETEXTMETRIC)AllocaM(BYTE, size); - GetOutlineTextMetrics(this->dc, size, otm); - - /* Font height is minimum height plus the difference between the default - * height for this font size and the small size. */ - int diff = scaled_height - ScaleFontTrad(_default_font_height[FS_SMALL]); - pixels = Clamp(std::min(otm->otmusMinimumPPEM, 20u) + diff, scaled_height, MAX_FONT_SIZE); - - SelectObject(dc, old); - DeleteObject(temp); - } - } else { - pixels = ScaleFontTrad(pixels); - } - this->used_size = pixels; - - /* Create GDI font handle. */ - this->logfont.lfHeight = -pixels; - this->logfont.lfWidth = 0; - this->logfont.lfOutPrecision = ANTIALIASED_QUALITY; - - if (this->font != nullptr) { - SelectObject(dc, this->old_font); - DeleteObject(this->font); - } - this->font = CreateFontIndirect(&this->logfont); - this->old_font = SelectObject(this->dc, this->font); - - /* Query the font metrics we needed. */ - UINT otmSize = GetOutlineTextMetrics(this->dc, 0, nullptr); - POUTLINETEXTMETRIC otm = (POUTLINETEXTMETRIC)AllocaM(BYTE, otmSize); - GetOutlineTextMetrics(this->dc, otmSize, otm); - - this->units_per_em = otm->otmEMSquare; - this->ascender = otm->otmTextMetrics.tmAscent; - this->descender = otm->otmTextMetrics.tmDescent; - this->height = this->ascender + this->descender; - this->glyph_size.cx = otm->otmTextMetrics.tmMaxCharWidth; - this->glyph_size.cy = otm->otmTextMetrics.tmHeight; - - DEBUG(freetype, 2, "Loaded font '%s' with size %d", FS2OTTD((LPTSTR)((BYTE *)otm + (ptrdiff_t)otm->otmpFullName)), pixels); -} - -/** - * Reset cached glyphs. - */ -void Win32FontCache::ClearFontCache() -{ - /* GUI scaling might have changed, determine font size anew if it was automatically selected. */ - if (this->font != nullptr) this->SetFontSize(this->fs, this->req_size); - - this->TrueTypeFontCache::ClearFontCache(); -} - -/* virtual */ const Sprite *Win32FontCache::InternalGetGlyph(GlyphID key, bool aa) -{ - GLYPHMETRICS gm; - MAT2 mat = { {0, 1}, {0, 0}, {0, 0}, {0, 1} }; - - /* Make a guess for the needed memory size. */ - DWORD size = this->glyph_size.cy * Align(aa ? this->glyph_size.cx : std::max(this->glyph_size.cx / 8l, 1l), 4); // Bitmap data is DWORD-aligned rows. - byte *bmp = AllocaM(byte, size); - size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat); - - if (size == GDI_ERROR) { - /* No dice with the guess. First query size of needed glyph memory, then allocate the - * memory and query again. This dance is necessary as some glyphs will only render with - * the exact matching size; e.g. the space glyph has no pixels and must be requested - * with size == 0, anything else fails. Unfortunately, a failed call doesn't return any - * info about the size and thus the triple GetGlyphOutline()-call. */ - size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, 0, nullptr, &mat); - if (size == GDI_ERROR) usererror("Unable to render font glyph"); - bmp = AllocaM(byte, size); - GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat); - } - - /* Add 1 pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel. */ - uint width = std::max(1U, (uint)gm.gmBlackBoxX + (this->fs == FS_NORMAL)); - uint height = std::max(1U, (uint)gm.gmBlackBoxY + (this->fs == FS_NORMAL)); - - /* Limit glyph size to prevent overflows later on. */ - if (width > 256 || height > 256) usererror("Font glyph is too large"); - - /* GDI has rendered the glyph, now we allocate a sprite and copy the image into it. */ - SpriteLoader::Sprite sprite; - sprite.AllocateData(ZOOM_LVL_NORMAL, width * height); - sprite.type = ST_FONT; - sprite.width = width; - sprite.height = height; - sprite.x_offs = gm.gmptGlyphOrigin.x; - sprite.y_offs = this->ascender - gm.gmptGlyphOrigin.y; - - if (size > 0) { - /* All pixel data returned by GDI is in the form of DWORD-aligned rows. - * For a non anti-aliased glyph, the returned bitmap has one bit per pixel. - * For anti-aliased rendering, GDI uses the strange value range of 0 to 64, - * inclusively. To map this to 0 to 255, we shift left by two and then - * subtract one. */ - uint pitch = Align(aa ? gm.gmBlackBoxX : std::max(gm.gmBlackBoxX / 8u, 1u), 4); - - /* Draw shadow for medium size. */ - if (this->fs == FS_NORMAL && !aa) { - for (uint y = 0; y < gm.gmBlackBoxY; y++) { - for (uint x = 0; x < gm.gmBlackBoxX; x++) { - if (aa ? (bmp[x + y * pitch] > 0) : HasBit(bmp[(x / 8) + y * pitch], 7 - (x % 8))) { - sprite.data[1 + x + (1 + y) * sprite.width].m = SHADOW_COLOUR; - sprite.data[1 + x + (1 + y) * sprite.width].a = aa ? (bmp[x + y * pitch] << 2) - 1 : 0xFF; - } - } - } - } - - for (uint y = 0; y < gm.gmBlackBoxY; y++) { - for (uint x = 0; x < gm.gmBlackBoxX; x++) { - if (aa ? (bmp[x + y * pitch] > 0) : HasBit(bmp[(x / 8) + y * pitch], 7 - (x % 8))) { - sprite.data[x + y * sprite.width].m = FACE_COLOUR; - sprite.data[x + y * sprite.width].a = aa ? (bmp[x + y * pitch] << 2) - 1 : 0xFF; - } - } - } - } - - GlyphEntry new_glyph; - new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(&sprite, AllocateFont); - new_glyph.width = gm.gmCellIncX; - - this->SetGlyphPtr(key, &new_glyph); - - return new_glyph.sprite; -} - -/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(WChar key) -{ - assert(IsPrintable(key)); - - if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { - return this->parent->MapCharToGlyph(key); - } - - /* Convert characters outside of the BMP into surrogate pairs. */ - WCHAR chars[2]; - if (key >= 0x010000U) { - chars[0] = (WCHAR)(((key - 0x010000U) >> 10) + 0xD800); - chars[1] = (WCHAR)(((key - 0x010000U) & 0x3FF) + 0xDC00); - } else { - chars[0] = (WCHAR)(key & 0xFFFF); - } - - WORD glyphs[2] = {0, 0}; - GetGlyphIndicesW(this->dc, chars, key >= 0x010000U ? 2 : 1, glyphs, GGI_MARK_NONEXISTING_GLYPHS); - - return glyphs[0] != 0xFFFF ? glyphs[0] : 0; -} - -/* virtual */ const void *Win32FontCache::InternalGetFontTable(uint32 tag, size_t &length) -{ - DWORD len = GetFontData(this->dc, tag, 0, nullptr, 0); - - void *result = nullptr; - if (len != GDI_ERROR && len > 0) { - result = MallocT(len); - GetFontData(this->dc, tag, 0, result, len); - } - - length = len; - return result; -} - -/** - * Loads the GDI font. - * If a GDI font description is present, e.g. from the automatic font - * fallback search, use it. Otherwise, try to resolve it by font name. - * @param fs The font size to load. - */ -static void LoadWin32Font(FontSize fs) -{ - static const char *SIZE_TO_NAME[] = { "medium", "small", "large", "mono" }; - - FreeTypeSubSetting *settings = nullptr; - switch (fs) { - default: NOT_REACHED(); - case FS_SMALL: settings = &_freetype.small; break; - case FS_NORMAL: settings = &_freetype.medium; break; - case FS_LARGE: settings = &_freetype.large; break; - case FS_MONO: settings = &_freetype.mono; break; - } - - if (StrEmpty(settings->font)) return; - - LOGFONT logfont; - MemSetT(&logfont, 0); - logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH; - logfont.lfCharSet = DEFAULT_CHARSET; - logfont.lfOutPrecision = OUT_OUTLINE_PRECIS; - logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; - - if (settings->os_handle != nullptr) { - logfont = *(const LOGFONT *)settings->os_handle; - } else if (strchr(settings->font, '.') != nullptr) { - /* Might be a font file name, try load it. */ - - TCHAR fontPath[MAX_PATH] = {}; - - /* See if this is an absolute path. */ - if (FileExists(settings->font)) { - convert_to_fs(settings->font, fontPath, lengthof(fontPath), false); - } else { - /* Scan the search-paths to see if it can be found. */ - std::string full_font = FioFindFullPath(BASE_DIR, settings->font); - if (!full_font.empty()) { - convert_to_fs(full_font.c_str(), fontPath, lengthof(fontPath), false); - } - } - - if (fontPath[0] != 0) { - if (AddFontResourceEx(fontPath, FR_PRIVATE, 0) != 0) { - /* Try a nice little undocumented function first for getting the internal font name. - * Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */ - typedef BOOL(WINAPI * PFNGETFONTRESOURCEINFO)(LPCTSTR, LPDWORD, LPVOID, DWORD); -#ifdef UNICODE - static PFNGETFONTRESOURCEINFO GetFontResourceInfo = (PFNGETFONTRESOURCEINFO)GetProcAddress(GetModuleHandle(_T("Gdi32")), "GetFontResourceInfoW"); -#else - static PFNGETFONTRESOURCEINFO GetFontResourceInfo = (PFNGETFONTRESOURCEINFO)GetProcAddress(GetModuleHandle(_T("Gdi32")), "GetFontResourceInfoA"); -#endif - - if (GetFontResourceInfo != nullptr) { - /* Try to query an array of LOGFONTs that describe the file. */ - DWORD len = 0; - if (GetFontResourceInfo(fontPath, &len, nullptr, 2) && len >= sizeof(LOGFONT)) { - LOGFONT *buf = (LOGFONT *)AllocaM(byte, len); - if (GetFontResourceInfo(fontPath, &len, buf, 2)) { - logfont = *buf; // Just use first entry. - } - } - } - - /* No dice yet. Use the file name as the font face name, hoping it matches. */ - if (logfont.lfFaceName[0] == 0) { - TCHAR fname[_MAX_FNAME]; - _tsplitpath(fontPath, nullptr, nullptr, fname, nullptr); - - _tcsncpy_s(logfont.lfFaceName, lengthof(logfont.lfFaceName), fname, _TRUNCATE); - logfont.lfWeight = strcasestr(settings->font, " bold") != nullptr || strcasestr(settings->font, "-bold") != nullptr ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. - } - } else { - ShowInfoF("Unable to load file '%s' for %s font, using default windows font selection instead", settings->font, SIZE_TO_NAME[fs]); - } - } - } - - if (logfont.lfFaceName[0] == 0) { - logfont.lfWeight = strcasestr(settings->font, " bold") != nullptr ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. - convert_to_fs(settings->font, logfont.lfFaceName, lengthof(logfont.lfFaceName), false); - } - - HFONT font = CreateFontIndirect(&logfont); - if (font == nullptr) { - ShowInfoF("Unable to use '%s' for %s font, Win32 reported error 0x%lX, using sprite font instead", settings->font, SIZE_TO_NAME[fs], GetLastError()); - return; - } - DeleteObject(font); - - new Win32FontCache(fs, logfont, settings->size); -} #endif /* WITH_FREETYPE */ @@ -1075,6 +701,7 @@ void InitFreeType(bool monospace) #ifdef WITH_FREETYPE LoadFreeTypeFont(fs); #elif defined(_WIN32) + extern void LoadWin32Font(FontSize fs); LoadWin32Font(fs); #endif } diff --git a/src/fontcache.h b/src/fontcache.h index fac08a3537..4b26af8713 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -28,6 +28,9 @@ protected: int ascender; ///< The ascender value of the font. int descender; ///< The descender value of the font. int units_per_em; ///< The units per EM value of the font. + + static int GetDefaultFontHeight(FontSize fs); + public: FontCache(FontSize fs); virtual ~FontCache(); @@ -209,8 +212,6 @@ static inline bool GetDrawGlyphShadow(FontSize size) return FontCache::Get(size)->GetDrawGlyphShadow(); } -#if defined(WITH_FREETYPE) || defined(_WIN32) - /** Settings for a single freetype font. */ struct FreeTypeSubSetting { char font[MAX_PATH]; ///< The name of the font, or path to the font. @@ -230,8 +231,6 @@ struct FreeTypeSettings { extern FreeTypeSettings _freetype; -#endif /* defined(WITH_FREETYPE) || defined(_WIN32) */ - void InitFreeType(bool monospace); void UninitFreeType(); diff --git a/src/fontcache_internal.h b/src/fontcache_internal.h new file mode 100644 index 0000000000..09e676234f --- /dev/null +++ b/src/fontcache_internal.h @@ -0,0 +1,76 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** @file fontcache_internal.h Support types and functions for platform-specific font support. */ + +#ifndef FONTCACHE_INTERNAL_H +#define FONTCACHE_INTERNAL_H + +#include "core/smallmap_type.hpp" +#include "fontcache.h" + + +static const int MAX_FONT_SIZE = 72; ///< Maximum font size. + +static const byte FACE_COLOUR = 1; +static const byte SHADOW_COLOUR = 2; + +/** Font cache for fonts that are based on a TrueType font. */ +class TrueTypeFontCache : public FontCache { +protected: + int req_size; ///< Requested font size. + int used_size; ///< Used font size. + + typedef SmallMap > FontTable; ///< Table with font table cache + FontTable font_tables; ///< Cached font tables. + + /** Container for information about a glyph. */ + struct GlyphEntry { + Sprite *sprite; ///< The loaded sprite. + byte width; ///< The width of the glyph. + bool duplicate; ///< Whether this glyph entry is a duplicate, i.e. may this be freed? + }; + + /** + * The glyph cache. This is structured to reduce memory consumption. + * 1) There is a 'segment' table for each font size. + * 2) Each segment table is a discrete block of characters. + * 3) Each block contains 256 (aligned) characters sequential characters. + * + * The cache is accessed in the following way: + * For character 0x0041 ('A'): glyph_to_sprite[0x00][0x41] + * For character 0x20AC (Euro): glyph_to_sprite[0x20][0xAC] + * + * Currently only 256 segments are allocated, "limiting" us to 65536 characters. + * This can be simply changed in the two functions Get & SetGlyphPtr. + */ + GlyphEntry **glyph_to_sprite; + + GlyphEntry *GetGlyphPtr(GlyphID key); + void SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool duplicate = false); + + virtual const void *InternalGetFontTable(uint32 tag, size_t &length) = 0; + virtual const Sprite *InternalGetGlyph(GlyphID key, bool aa) = 0; + +public: + TrueTypeFontCache(FontSize fs, int pixels); + virtual ~TrueTypeFontCache(); + int GetFontSize() const override { return this->used_size; } + SpriteID GetUnicodeGlyph(WChar key) override { return this->parent->GetUnicodeGlyph(key); } + void SetUnicodeGlyph(WChar key, SpriteID sprite) override { this->parent->SetUnicodeGlyph(key, sprite); } + void InitializeUnicodeGlyphMap() override { this->parent->InitializeUnicodeGlyphMap(); } + const Sprite *GetGlyph(GlyphID key) override; + const void *GetFontTable(uint32 tag, size_t &length) override; + void ClearFontCache() override; + uint GetGlyphWidth(GlyphID key) override; + bool GetDrawGlyphShadow() override; + bool IsBuiltInFont() override { return false; } +}; + +void *AllocateFont(size_t size); + +#endif /* FONTCACHE_INTERNAL_H */ diff --git a/src/fontdetection.cpp b/src/fontdetection.cpp index dfb27a35de..ca9f620b9b 100644 --- a/src/fontdetection.cpp +++ b/src/fontdetection.cpp @@ -24,357 +24,7 @@ extern FT_Library _library; * If no appropriate font is found, the function returns an error */ -/* ======================================================================================== - * Windows support - * ======================================================================================== */ - -#ifdef _WIN32 -#include "core/alloc_func.hpp" -#include "core/math_func.hpp" -#include -#include /* SHGetFolderPath */ -#include "os/windows/win32.h" - -#include "safeguards.h" - -#ifdef WITH_FREETYPE -/** - * Get the short DOS 8.3 format for paths. - * FreeType doesn't support Unicode filenames and Windows' fopen (as used - * by FreeType) doesn't support UTF-8 filenames. So we have to convert the - * filename into something that isn't UTF-8 but represents the Unicode file - * name. This is the short DOS 8.3 format. This does not contain any - * characters that fopen doesn't support. - * @param long_path the path in system encoding. - * @return the short path in ANSI (ASCII). - */ -const char *GetShortPath(const TCHAR *long_path) -{ - static char short_path[MAX_PATH]; -#ifdef UNICODE - WCHAR short_path_w[MAX_PATH]; - GetShortPathName(long_path, short_path_w, lengthof(short_path_w)); - WideCharToMultiByte(CP_ACP, 0, short_path_w, -1, short_path, lengthof(short_path), nullptr, nullptr); -#else - /* Technically not needed, but do it for consistency. */ - GetShortPathName(long_path, short_path, lengthof(short_path)); -#endif - return short_path; -} - -/* Get the font file to be loaded into Freetype by looping the registry - * location where windows lists all installed fonts. Not very nice, will - * surely break if the registry path changes, but it works. Much better - * solution would be to use CreateFont, and extract the font data from it - * by GetFontData. The problem with this is that the font file needs to be - * kept in memory then until the font is no longer needed. This could mean - * an additional memory usage of 30MB (just for fonts!) when using an eastern - * font for all font sizes */ -#define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" -#define FONT_DIR_9X "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts" -FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) -{ - FT_Error err = FT_Err_Cannot_Open_Resource; - HKEY hKey; - LONG ret; - TCHAR vbuffer[MAX_PATH], dbuffer[256]; - TCHAR *pathbuf; - const char *font_path; - uint index; - size_t path_len; - - /* On windows NT (2000, NT3.5, XP, etc.) the fonts are stored in the - * "Windows NT" key, on Windows 9x in the Windows key. To save us having - * to retrieve the windows version, we'll just query both */ - ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_NT), 0, KEY_READ, &hKey); - if (ret != ERROR_SUCCESS) ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_9X), 0, KEY_READ, &hKey); - - if (ret != ERROR_SUCCESS) { - DEBUG(freetype, 0, "Cannot open registry key HKLM\\SOFTWARE\\Microsoft\\Windows (NT)\\CurrentVersion\\Fonts"); - return err; - } - - /* Convert font name to file system encoding. */ - TCHAR *font_namep = _tcsdup(OTTD2FS(font_name)); - - for (index = 0;; index++) { - TCHAR *s; - DWORD vbuflen = lengthof(vbuffer); - DWORD dbuflen = lengthof(dbuffer); - - ret = RegEnumValue(hKey, index, vbuffer, &vbuflen, nullptr, nullptr, (byte*)dbuffer, &dbuflen); - if (ret != ERROR_SUCCESS) goto registry_no_font_found; - - /* The font names in the registry are of the following 3 forms: - * - ADMUI3.fon - * - Book Antiqua Bold (TrueType) - * - Batang & BatangChe & Gungsuh & GungsuhChe (TrueType) - * We will strip the font-type '()' if any and work with the font name - * itself, which must match exactly; if... - * TTC files, font files which contain more than one font are separated - * by '&'. Our best bet will be to do substr match for the fontname - * and then let FreeType figure out which index to load */ - s = _tcschr(vbuffer, _T('(')); - if (s != nullptr) s[-1] = '\0'; - - if (_tcschr(vbuffer, _T('&')) == nullptr) { - if (_tcsicmp(vbuffer, font_namep) == 0) break; - } else { - if (_tcsstr(vbuffer, font_namep) != nullptr) break; - } - } - - if (!SUCCEEDED(OTTDSHGetFolderPath(nullptr, CSIDL_FONTS, nullptr, SHGFP_TYPE_CURRENT, vbuffer))) { - DEBUG(freetype, 0, "SHGetFolderPath cannot return fonts directory"); - goto folder_error; - } - - /* Some fonts are contained in .ttc files, TrueType Collection fonts. These - * contain multiple fonts inside this single file. GetFontData however - * returns the whole file, so we need to check each font inside to get the - * proper font. */ - path_len = _tcslen(vbuffer) + _tcslen(dbuffer) + 2; // '\' and terminating nul. - pathbuf = AllocaM(TCHAR, path_len); - _sntprintf(pathbuf, path_len, _T("%s\\%s"), vbuffer, dbuffer); - - /* Convert the path into something that FreeType understands. */ - font_path = GetShortPath(pathbuf); - - index = 0; - do { - err = FT_New_Face(_library, font_path, index, face); - if (err != FT_Err_Ok) break; - - if (strncasecmp(font_name, (*face)->family_name, strlen((*face)->family_name)) == 0) break; - /* Try english name if font name failed */ - if (strncasecmp(font_name + strlen(font_name) + 1, (*face)->family_name, strlen((*face)->family_name)) == 0) break; - err = FT_Err_Cannot_Open_Resource; - - } while ((FT_Long)++index != (*face)->num_faces); - - -folder_error: -registry_no_font_found: - free(font_namep); - RegCloseKey(hKey); - return err; -} - -/** - * Fonts can have localised names and when the system locale is the same as - * one of those localised names Windows will always return that localised name - * instead of allowing to get the non-localised (English US) name of the font. - * This will later on give problems as freetype uses the non-localised name of - * the font and we need to compare based on that name. - * Windows furthermore DOES NOT have an API to get the non-localised name nor - * can we override the system locale. This means that we have to actually read - * the font itself to gather the font name we want. - * Based on: http://blogs.msdn.com/michkap/archive/2006/02/13/530814.aspx - * @param logfont the font information to get the english name of. - * @return the English name (if it could be found). - */ -static const char *GetEnglishFontName(const ENUMLOGFONTEX *logfont) -{ - static char font_name[MAX_PATH]; - const char *ret_font_name = nullptr; - uint pos = 0; - HDC dc; - HGDIOBJ oldfont; - byte *buf; - DWORD dw; - uint16 format, count, stringOffset, platformId, encodingId, languageId, nameId, length, offset; - - HFONT font = CreateFontIndirect(&logfont->elfLogFont); - if (font == nullptr) goto err1; - - dc = GetDC(nullptr); - oldfont = SelectObject(dc, font); - dw = GetFontData(dc, 'eman', 0, nullptr, 0); - if (dw == GDI_ERROR) goto err2; - - buf = MallocT(dw); - dw = GetFontData(dc, 'eman', 0, buf, dw); - if (dw == GDI_ERROR) goto err3; - - format = buf[pos++] << 8; - format += buf[pos++]; - assert(format == 0); - count = buf[pos++] << 8; - count += buf[pos++]; - stringOffset = buf[pos++] << 8; - stringOffset += buf[pos++]; - for (uint i = 0; i < count; i++) { - platformId = buf[pos++] << 8; - platformId += buf[pos++]; - encodingId = buf[pos++] << 8; - encodingId += buf[pos++]; - languageId = buf[pos++] << 8; - languageId += buf[pos++]; - nameId = buf[pos++] << 8; - nameId += buf[pos++]; - if (nameId != 1) { - pos += 4; // skip length and offset - continue; - } - length = buf[pos++] << 8; - length += buf[pos++]; - offset = buf[pos++] << 8; - offset += buf[pos++]; - - /* Don't buffer overflow */ - length = std::min(length, MAX_PATH - 1); - for (uint j = 0; j < length; j++) font_name[j] = buf[stringOffset + offset + j]; - font_name[length] = '\0'; - - if ((platformId == 1 && languageId == 0) || // Macintosh English - (platformId == 3 && languageId == 0x0409)) { // Microsoft English (US) - ret_font_name = font_name; - break; - } - } - -err3: - free(buf); -err2: - SelectObject(dc, oldfont); - ReleaseDC(nullptr, dc); - DeleteObject(font); -err1: - return ret_font_name == nullptr ? WIDE_TO_MB((const TCHAR*)logfont->elfFullName) : ret_font_name; -} -#endif /* WITH_FREETYPE */ - -class FontList { -protected: - TCHAR **fonts; - uint items; - uint capacity; - -public: - FontList() : fonts(nullptr), items(0), capacity(0) { }; - - ~FontList() { - if (this->fonts == nullptr) return; - - for (uint i = 0; i < this->items; i++) { - free(this->fonts[i]); - } - - free(this->fonts); - } - - bool Add(const TCHAR *font) { - for (uint i = 0; i < this->items; i++) { - if (_tcscmp(this->fonts[i], font) == 0) return false; - } - - if (this->items == this->capacity) { - this->capacity += 10; - this->fonts = ReallocT(this->fonts, this->capacity); - } - - this->fonts[this->items++] = _tcsdup(font); - - return true; - } -}; - -struct EFCParam { - FreeTypeSettings *settings; - LOCALESIGNATURE locale; - MissingGlyphSearcher *callback; - FontList fonts; -}; - -static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam) -{ - EFCParam *info = (EFCParam *)lParam; - - /* Skip duplicates */ - if (!info->fonts.Add((const TCHAR*)logfont->elfFullName)) return 1; - /* Only use TrueType fonts */ - if (!(type & TRUETYPE_FONTTYPE)) return 1; - /* Don't use SYMBOL fonts */ - if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1; - /* Use monospaced fonts when asked for it. */ - if (info->callback->Monospace() && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1; - - /* The font has to have at least one of the supported locales to be usable. */ - if ((metric->ntmFontSig.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (metric->ntmFontSig.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) { - /* On win9x metric->ntmFontSig seems to contain garbage. */ - FONTSIGNATURE fs; - memset(&fs, 0, sizeof(fs)); - HFONT font = CreateFontIndirect(&logfont->elfLogFont); - if (font != nullptr) { - HDC dc = GetDC(nullptr); - HGDIOBJ oldfont = SelectObject(dc, font); - GetTextCharsetInfo(dc, &fs, 0); - SelectObject(dc, oldfont); - ReleaseDC(nullptr, dc); - DeleteObject(font); - } - if ((fs.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (fs.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) return 1; - } - - char font_name[MAX_PATH]; - convert_from_fs((const TCHAR *)logfont->elfFullName, font_name, lengthof(font_name)); - -#ifdef WITH_FREETYPE - /* Add english name after font name */ - const char *english_name = GetEnglishFontName(logfont); - strecpy(font_name + strlen(font_name) + 1, english_name, lastof(font_name)); - - /* Check whether we can actually load the font. */ - bool ft_init = _library != nullptr; - bool found = false; - FT_Face face; - /* Init FreeType if needed. */ - if ((ft_init || FT_Init_FreeType(&_library) == FT_Err_Ok) && GetFontByFaceName(font_name, &face) == FT_Err_Ok) { - FT_Done_Face(face); - found = true; - } - if (!ft_init) { - /* Uninit FreeType if we did the init. */ - FT_Done_FreeType(_library); - _library = nullptr; - } - - if (!found) return 1; -#else - const char *english_name = font_name; -#endif /* WITH_FREETYPE */ - - info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont); - if (info->callback->FindMissingGlyphs()) return 1; - DEBUG(freetype, 1, "Fallback font: %s (%s)", font_name, english_name); - return 0; // stop enumerating -} - -bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) -{ - DEBUG(freetype, 1, "Trying fallback fonts"); - EFCParam langInfo; - if (GetLocaleInfo(MAKELCID(winlangid, SORT_DEFAULT), LOCALE_FONTSIGNATURE, (LPTSTR)&langInfo.locale, sizeof(langInfo.locale) / sizeof(TCHAR)) == 0) { - /* Invalid langid or some other mysterious error, can't determine fallback font. */ - DEBUG(freetype, 1, "Can't get locale info for fallback font (langid=0x%x)", winlangid); - return false; - } - langInfo.settings = settings; - langInfo.callback = callback; - - LOGFONT font; - /* Enumerate all fonts. */ - font.lfCharSet = DEFAULT_CHARSET; - font.lfFaceName[0] = '\0'; - font.lfPitchAndFamily = 0; - - HDC dc = GetDC(nullptr); - int ret = EnumFontFamiliesEx(dc, &font, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&langInfo, 0); - ReleaseDC(nullptr, dc); - return ret == 0; -} - -#elif defined(__APPLE__) /* end ifdef Win32 */ +#if defined(__APPLE__) /* ======================================================================================== * OSX support * ======================================================================================== */ @@ -643,10 +293,15 @@ bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, i FcFini(); return ret; } +#endif /* end ifdef WITH_FONTCONFIG */ -#else /* without WITH_FONTCONFIG */ +#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) + +#ifdef WITH_FREETYPE FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) {return FT_Err_Cannot_Open_Resource;} +#endif /* WITH_FREETYPE */ + bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) { return false; } -#endif /* WITH_FONTCONFIG */ +#endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) */ #endif /* WITH_FREETYPE */ diff --git a/src/fontdetection.h b/src/fontdetection.h index 70b2fe236e..9cf39d2b07 100644 --- a/src/fontdetection.h +++ b/src/fontdetection.h @@ -27,7 +27,6 @@ FT_Error GetFontByFaceName(const char *font_name, FT_Face *face); #endif /* WITH_FREETYPE */ -#if defined(WITH_FREETYPE) || defined(_WIN32) /** * We would like to have a fallback font as the current one * doesn't contain all characters we need. @@ -38,8 +37,6 @@ FT_Error GetFontByFaceName(const char *font_name, FT_Face *face); * @param callback The function to call to check for missing glyphs. * @return true if a font has been set, false otherwise. */ -bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, class MissingGlyphSearcher *callback); - -#endif /* defined(WITH_FREETYPE) || defined(WIN32)*/ +bool SetFallbackFont(struct FreeTypeSettings *settings, const char *language_isocode, int winlangid, class MissingGlyphSearcher *callback); #endif diff --git a/src/os/windows/CMakeLists.txt b/src/os/windows/CMakeLists.txt index 19d1bd46e3..8ac2de7acc 100644 --- a/src/os/windows/CMakeLists.txt +++ b/src/os/windows/CMakeLists.txt @@ -1,5 +1,7 @@ add_files( crashlog_win.cpp + font_win32.cpp + font_win32.h string_uniscribe.cpp string_uniscribe.h win32.cpp diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp new file mode 100644 index 0000000000..4bcbb145d9 --- /dev/null +++ b/src/os/windows/font_win32.cpp @@ -0,0 +1,675 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** @file font_win32.cpp Functions related to font handling on Win32. */ + +#include "../../stdafx.h" +#include "../../debug.h" +#include "font_win32.h" +#include "../../blitter/factory.hpp" +#include "../../core/alloc_func.hpp" +#include "../../core/math_func.hpp" +#include "../../fileio_func.h" +#include "../../fontdetection.h" +#include "../../fontcache.h" +#include "../../string_func.h" +#include "../../strings_func.h" +#include "../../zoom_func.h" + +#include "../../table/control_codes.h" + +#include +#include /* SHGetFolderPath */ +#include "os/windows/win32.h" +#undef small // Say what, Windows? + +#include "safeguards.h" + +#ifdef WITH_FREETYPE + +#include +#include FT_FREETYPE_H + +extern FT_Library _library; + +/** + * Get the short DOS 8.3 format for paths. + * FreeType doesn't support Unicode filenames and Windows' fopen (as used + * by FreeType) doesn't support UTF-8 filenames. So we have to convert the + * filename into something that isn't UTF-8 but represents the Unicode file + * name. This is the short DOS 8.3 format. This does not contain any + * characters that fopen doesn't support. + * @param long_path the path in system encoding. + * @return the short path in ANSI (ASCII). + */ +static const char *GetShortPath(const TCHAR *long_path) +{ + static char short_path[MAX_PATH]; +#ifdef UNICODE + WCHAR short_path_w[MAX_PATH]; + GetShortPathName(long_path, short_path_w, lengthof(short_path_w)); + WideCharToMultiByte(CP_ACP, 0, short_path_w, -1, short_path, lengthof(short_path), nullptr, nullptr); +#else + /* Technically not needed, but do it for consistency. */ + GetShortPathName(long_path, short_path, lengthof(short_path)); +#endif + return short_path; +} + +/* Get the font file to be loaded into Freetype by looping the registry + * location where windows lists all installed fonts. Not very nice, will + * surely break if the registry path changes, but it works. Much better + * solution would be to use CreateFont, and extract the font data from it + * by GetFontData. The problem with this is that the font file needs to be + * kept in memory then until the font is no longer needed. This could mean + * an additional memory usage of 30MB (just for fonts!) when using an eastern + * font for all font sizes */ +#define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" +#define FONT_DIR_9X "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts" +FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) +{ + FT_Error err = FT_Err_Cannot_Open_Resource; + HKEY hKey; + LONG ret; + TCHAR vbuffer[MAX_PATH], dbuffer[256]; + TCHAR *pathbuf; + const char *font_path; + uint index; + size_t path_len; + + /* On windows NT (2000, NT3.5, XP, etc.) the fonts are stored in the + * "Windows NT" key, on Windows 9x in the Windows key. To save us having + * to retrieve the windows version, we'll just query both */ + ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_NT), 0, KEY_READ, &hKey); + if (ret != ERROR_SUCCESS) ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(FONT_DIR_9X), 0, KEY_READ, &hKey); + + if (ret != ERROR_SUCCESS) { + DEBUG(freetype, 0, "Cannot open registry key HKLM\\SOFTWARE\\Microsoft\\Windows (NT)\\CurrentVersion\\Fonts"); + return err; + } + + /* Convert font name to file system encoding. */ + TCHAR *font_namep = _tcsdup(OTTD2FS(font_name)); + + for (index = 0;; index++) { + TCHAR *s; + DWORD vbuflen = lengthof(vbuffer); + DWORD dbuflen = lengthof(dbuffer); + + ret = RegEnumValue(hKey, index, vbuffer, &vbuflen, nullptr, nullptr, (byte *)dbuffer, &dbuflen); + if (ret != ERROR_SUCCESS) goto registry_no_font_found; + + /* The font names in the registry are of the following 3 forms: + * - ADMUI3.fon + * - Book Antiqua Bold (TrueType) + * - Batang & BatangChe & Gungsuh & GungsuhChe (TrueType) + * We will strip the font-type '()' if any and work with the font name + * itself, which must match exactly; if... + * TTC files, font files which contain more than one font are separated + * by '&'. Our best bet will be to do substr match for the fontname + * and then let FreeType figure out which index to load */ + s = _tcschr(vbuffer, _T('(')); + if (s != nullptr) s[-1] = '\0'; + + if (_tcschr(vbuffer, _T('&')) == nullptr) { + if (_tcsicmp(vbuffer, font_namep) == 0) break; + } else { + if (_tcsstr(vbuffer, font_namep) != nullptr) break; + } + } + + if (!SUCCEEDED(OTTDSHGetFolderPath(nullptr, CSIDL_FONTS, nullptr, SHGFP_TYPE_CURRENT, vbuffer))) { + DEBUG(freetype, 0, "SHGetFolderPath cannot return fonts directory"); + goto folder_error; + } + + /* Some fonts are contained in .ttc files, TrueType Collection fonts. These + * contain multiple fonts inside this single file. GetFontData however + * returns the whole file, so we need to check each font inside to get the + * proper font. */ + path_len = _tcslen(vbuffer) + _tcslen(dbuffer) + 2; // '\' and terminating nul. + pathbuf = AllocaM(TCHAR, path_len); + _sntprintf(pathbuf, path_len, _T("%s\\%s"), vbuffer, dbuffer); + + /* Convert the path into something that FreeType understands. */ + font_path = GetShortPath(pathbuf); + + index = 0; + do { + err = FT_New_Face(_library, font_path, index, face); + if (err != FT_Err_Ok) break; + + if (strncasecmp(font_name, (*face)->family_name, strlen((*face)->family_name)) == 0) break; + /* Try english name if font name failed */ + if (strncasecmp(font_name + strlen(font_name) + 1, (*face)->family_name, strlen((*face)->family_name)) == 0) break; + err = FT_Err_Cannot_Open_Resource; + + } while ((FT_Long)++index != (*face)->num_faces); + + +folder_error: +registry_no_font_found: + free(font_namep); + RegCloseKey(hKey); + return err; +} + +/** + * Fonts can have localised names and when the system locale is the same as + * one of those localised names Windows will always return that localised name + * instead of allowing to get the non-localised (English US) name of the font. + * This will later on give problems as freetype uses the non-localised name of + * the font and we need to compare based on that name. + * Windows furthermore DOES NOT have an API to get the non-localised name nor + * can we override the system locale. This means that we have to actually read + * the font itself to gather the font name we want. + * Based on: http://blogs.msdn.com/michkap/archive/2006/02/13/530814.aspx + * @param logfont the font information to get the english name of. + * @return the English name (if it could be found). + */ +static const char *GetEnglishFontName(const ENUMLOGFONTEX *logfont) +{ + static char font_name[MAX_PATH]; + const char *ret_font_name = nullptr; + uint pos = 0; + HDC dc; + HGDIOBJ oldfont; + byte *buf; + DWORD dw; + uint16 format, count, stringOffset, platformId, encodingId, languageId, nameId, length, offset; + + HFONT font = CreateFontIndirect(&logfont->elfLogFont); + if (font == nullptr) goto err1; + + dc = GetDC(nullptr); + oldfont = SelectObject(dc, font); + dw = GetFontData(dc, 'eman', 0, nullptr, 0); + if (dw == GDI_ERROR) goto err2; + + buf = MallocT(dw); + dw = GetFontData(dc, 'eman', 0, buf, dw); + if (dw == GDI_ERROR) goto err3; + + format = buf[pos++] << 8; + format += buf[pos++]; + assert(format == 0); + count = buf[pos++] << 8; + count += buf[pos++]; + stringOffset = buf[pos++] << 8; + stringOffset += buf[pos++]; + for (uint i = 0; i < count; i++) { + platformId = buf[pos++] << 8; + platformId += buf[pos++]; + encodingId = buf[pos++] << 8; + encodingId += buf[pos++]; + languageId = buf[pos++] << 8; + languageId += buf[pos++]; + nameId = buf[pos++] << 8; + nameId += buf[pos++]; + if (nameId != 1) { + pos += 4; // skip length and offset + continue; + } + length = buf[pos++] << 8; + length += buf[pos++]; + offset = buf[pos++] << 8; + offset += buf[pos++]; + + /* Don't buffer overflow */ + length = std::min(length, MAX_PATH - 1); + for (uint j = 0; j < length; j++) font_name[j] = buf[stringOffset + offset + j]; + font_name[length] = '\0'; + + if ((platformId == 1 && languageId == 0) || // Macintosh English + (platformId == 3 && languageId == 0x0409)) { // Microsoft English (US) + ret_font_name = font_name; + break; + } + } + +err3: + free(buf); +err2: + SelectObject(dc, oldfont); + ReleaseDC(nullptr, dc); + DeleteObject(font); +err1: + return ret_font_name == nullptr ? WIDE_TO_MB((const TCHAR *)logfont->elfFullName) : ret_font_name; +} +#endif /* WITH_FREETYPE */ + +class FontList { +protected: + TCHAR **fonts; + uint items; + uint capacity; + +public: + FontList() : fonts(nullptr), items(0), capacity(0) { }; + + ~FontList() { + if (this->fonts == nullptr) return; + + for (uint i = 0; i < this->items; i++) { + free(this->fonts[i]); + } + + free(this->fonts); + } + + bool Add(const TCHAR *font) { + for (uint i = 0; i < this->items; i++) { + if (_tcscmp(this->fonts[i], font) == 0) return false; + } + + if (this->items == this->capacity) { + this->capacity += 10; + this->fonts = ReallocT(this->fonts, this->capacity); + } + + this->fonts[this->items++] = _tcsdup(font); + + return true; + } +}; + +struct EFCParam { + FreeTypeSettings *settings; + LOCALESIGNATURE locale; + MissingGlyphSearcher *callback; + FontList fonts; +}; + +static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam) +{ + EFCParam *info = (EFCParam *)lParam; + + /* Skip duplicates */ + if (!info->fonts.Add((const TCHAR *)logfont->elfFullName)) return 1; + /* Only use TrueType fonts */ + if (!(type & TRUETYPE_FONTTYPE)) return 1; + /* Don't use SYMBOL fonts */ + if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1; + /* Use monospaced fonts when asked for it. */ + if (info->callback->Monospace() && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1; + + /* The font has to have at least one of the supported locales to be usable. */ + if ((metric->ntmFontSig.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (metric->ntmFontSig.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) { + /* On win9x metric->ntmFontSig seems to contain garbage. */ + FONTSIGNATURE fs; + memset(&fs, 0, sizeof(fs)); + HFONT font = CreateFontIndirect(&logfont->elfLogFont); + if (font != nullptr) { + HDC dc = GetDC(nullptr); + HGDIOBJ oldfont = SelectObject(dc, font); + GetTextCharsetInfo(dc, &fs, 0); + SelectObject(dc, oldfont); + ReleaseDC(nullptr, dc); + DeleteObject(font); + } + if ((fs.fsCsb[0] & info->locale.lsCsbSupported[0]) == 0 && (fs.fsCsb[1] & info->locale.lsCsbSupported[1]) == 0) return 1; + } + + char font_name[MAX_PATH]; + convert_from_fs((const TCHAR *)logfont->elfFullName, font_name, lengthof(font_name)); + +#ifdef WITH_FREETYPE + /* Add english name after font name */ + const char *english_name = GetEnglishFontName(logfont); + strecpy(font_name + strlen(font_name) + 1, english_name, lastof(font_name)); + + /* Check whether we can actually load the font. */ + bool ft_init = _library != nullptr; + bool found = false; + FT_Face face; + /* Init FreeType if needed. */ + if ((ft_init || FT_Init_FreeType(&_library) == FT_Err_Ok) && GetFontByFaceName(font_name, &face) == FT_Err_Ok) { + FT_Done_Face(face); + found = true; + } + if (!ft_init) { + /* Uninit FreeType if we did the init. */ + FT_Done_FreeType(_library); + _library = nullptr; + } + + if (!found) return 1; +#else + const char *english_name = font_name; +#endif /* WITH_FREETYPE */ + + info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont); + if (info->callback->FindMissingGlyphs()) return 1; + DEBUG(freetype, 1, "Fallback font: %s (%s)", font_name, english_name); + return 0; // stop enumerating +} + +bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) +{ + DEBUG(freetype, 1, "Trying fallback fonts"); + EFCParam langInfo; + if (GetLocaleInfo(MAKELCID(winlangid, SORT_DEFAULT), LOCALE_FONTSIGNATURE, (LPTSTR)&langInfo.locale, sizeof(langInfo.locale) / sizeof(TCHAR)) == 0) { + /* Invalid langid or some other mysterious error, can't determine fallback font. */ + DEBUG(freetype, 1, "Can't get locale info for fallback font (langid=0x%x)", winlangid); + return false; + } + langInfo.settings = settings; + langInfo.callback = callback; + + LOGFONT font; + /* Enumerate all fonts. */ + font.lfCharSet = DEFAULT_CHARSET; + font.lfFaceName[0] = '\0'; + font.lfPitchAndFamily = 0; + + HDC dc = GetDC(nullptr); + int ret = EnumFontFamiliesEx(dc, &font, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&langInfo, 0); + ReleaseDC(nullptr, dc); + return ret == 0; +} + + +#ifndef ANTIALIASED_QUALITY +#define ANTIALIASED_QUALITY 4 +#endif + +/** + * Create a new Win32FontCache. + * @param fs The font size that is going to be cached. + * @param logfont The font that has to be loaded. + * @param pixels The number of pixels this font should be high. + */ +Win32FontCache::Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels) : TrueTypeFontCache(fs, pixels), logfont(logfont) +{ + this->dc = CreateCompatibleDC(nullptr); + this->SetFontSize(fs, pixels); +} + +Win32FontCache::~Win32FontCache() +{ + this->ClearFontCache(); + DeleteDC(this->dc); + DeleteObject(this->font); +} + +void Win32FontCache::SetFontSize(FontSize fs, int pixels) +{ + if (pixels == 0) { + /* Try to determine a good height based on the minimal height recommended by the font. */ + int scaled_height = ScaleFontTrad(this->GetDefaultFontHeight(this->fs)); + pixels = scaled_height; + + HFONT temp = CreateFontIndirect(&this->logfont); + if (temp != nullptr) { + HGDIOBJ old = SelectObject(this->dc, temp); + + UINT size = GetOutlineTextMetrics(this->dc, 0, nullptr); + LPOUTLINETEXTMETRIC otm = (LPOUTLINETEXTMETRIC)AllocaM(BYTE, size); + GetOutlineTextMetrics(this->dc, size, otm); + + /* Font height is minimum height plus the difference between the default + * height for this font size and the small size. */ + int diff = scaled_height - ScaleFontTrad(this->GetDefaultFontHeight(FS_SMALL)); + pixels = Clamp(std::min(otm->otmusMinimumPPEM, 20u) + diff, scaled_height, MAX_FONT_SIZE); + + SelectObject(dc, old); + DeleteObject(temp); + } + } else { + pixels = ScaleFontTrad(pixels); + } + this->used_size = pixels; + + /* Create GDI font handle. */ + this->logfont.lfHeight = -pixels; + this->logfont.lfWidth = 0; + this->logfont.lfOutPrecision = ANTIALIASED_QUALITY; + + if (this->font != nullptr) { + SelectObject(dc, this->old_font); + DeleteObject(this->font); + } + this->font = CreateFontIndirect(&this->logfont); + this->old_font = SelectObject(this->dc, this->font); + + /* Query the font metrics we needed. */ + UINT otmSize = GetOutlineTextMetrics(this->dc, 0, nullptr); + POUTLINETEXTMETRIC otm = (POUTLINETEXTMETRIC)AllocaM(BYTE, otmSize); + GetOutlineTextMetrics(this->dc, otmSize, otm); + + this->units_per_em = otm->otmEMSquare; + this->ascender = otm->otmTextMetrics.tmAscent; + this->descender = otm->otmTextMetrics.tmDescent; + this->height = this->ascender + this->descender; + this->glyph_size.cx = otm->otmTextMetrics.tmMaxCharWidth; + this->glyph_size.cy = otm->otmTextMetrics.tmHeight; + + DEBUG(freetype, 2, "Loaded font '%s' with size %d", FS2OTTD((LPTSTR)((BYTE *)otm + (ptrdiff_t)otm->otmpFullName)), pixels); +} + +/** + * Reset cached glyphs. + */ +void Win32FontCache::ClearFontCache() +{ + /* GUI scaling might have changed, determine font size anew if it was automatically selected. */ + if (this->font != nullptr) this->SetFontSize(this->fs, this->req_size); + + this->TrueTypeFontCache::ClearFontCache(); +} + +/* virtual */ const Sprite *Win32FontCache::InternalGetGlyph(GlyphID key, bool aa) +{ + GLYPHMETRICS gm; + MAT2 mat = { {0, 1}, {0, 0}, {0, 0}, {0, 1} }; + + /* Make a guess for the needed memory size. */ + DWORD size = this->glyph_size.cy * Align(aa ? this->glyph_size.cx : std::max(this->glyph_size.cx / 8l, 1l), 4); // Bitmap data is DWORD-aligned rows. + byte *bmp = AllocaM(byte, size); + size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat); + + if (size == GDI_ERROR) { + /* No dice with the guess. First query size of needed glyph memory, then allocate the + * memory and query again. This dance is necessary as some glyphs will only render with + * the exact matching size; e.g. the space glyph has no pixels and must be requested + * with size == 0, anything else fails. Unfortunately, a failed call doesn't return any + * info about the size and thus the triple GetGlyphOutline()-call. */ + size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, 0, nullptr, &mat); + if (size == GDI_ERROR) usererror("Unable to render font glyph"); + bmp = AllocaM(byte, size); + GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat); + } + + /* Add 1 pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel. */ + uint width = std::max(1U, (uint)gm.gmBlackBoxX + (this->fs == FS_NORMAL)); + uint height = std::max(1U, (uint)gm.gmBlackBoxY + (this->fs == FS_NORMAL)); + + /* Limit glyph size to prevent overflows later on. */ + if (width > 256 || height > 256) usererror("Font glyph is too large"); + + /* GDI has rendered the glyph, now we allocate a sprite and copy the image into it. */ + SpriteLoader::Sprite sprite; + sprite.AllocateData(ZOOM_LVL_NORMAL, width * height); + sprite.type = ST_FONT; + sprite.width = width; + sprite.height = height; + sprite.x_offs = gm.gmptGlyphOrigin.x; + sprite.y_offs = this->ascender - gm.gmptGlyphOrigin.y; + + if (size > 0) { + /* All pixel data returned by GDI is in the form of DWORD-aligned rows. + * For a non anti-aliased glyph, the returned bitmap has one bit per pixel. + * For anti-aliased rendering, GDI uses the strange value range of 0 to 64, + * inclusively. To map this to 0 to 255, we shift left by two and then + * subtract one. */ + uint pitch = Align(aa ? gm.gmBlackBoxX : std::max(gm.gmBlackBoxX / 8u, 1u), 4); + + /* Draw shadow for medium size. */ + if (this->fs == FS_NORMAL && !aa) { + for (uint y = 0; y < gm.gmBlackBoxY; y++) { + for (uint x = 0; x < gm.gmBlackBoxX; x++) { + if (aa ? (bmp[x + y * pitch] > 0) : HasBit(bmp[(x / 8) + y * pitch], 7 - (x % 8))) { + sprite.data[1 + x + (1 + y) * sprite.width].m = SHADOW_COLOUR; + sprite.data[1 + x + (1 + y) * sprite.width].a = aa ? (bmp[x + y * pitch] << 2) - 1 : 0xFF; + } + } + } + } + + for (uint y = 0; y < gm.gmBlackBoxY; y++) { + for (uint x = 0; x < gm.gmBlackBoxX; x++) { + if (aa ? (bmp[x + y * pitch] > 0) : HasBit(bmp[(x / 8) + y * pitch], 7 - (x % 8))) { + sprite.data[x + y * sprite.width].m = FACE_COLOUR; + sprite.data[x + y * sprite.width].a = aa ? (bmp[x + y * pitch] << 2) - 1 : 0xFF; + } + } + } + } + + GlyphEntry new_glyph; + new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(&sprite, AllocateFont); + new_glyph.width = gm.gmCellIncX; + + this->SetGlyphPtr(key, &new_glyph); + + return new_glyph.sprite; +} + +/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(WChar key) +{ + assert(IsPrintable(key)); + + if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { + return this->parent->MapCharToGlyph(key); + } + + /* Convert characters outside of the BMP into surrogate pairs. */ + WCHAR chars[2]; + if (key >= 0x010000U) { + chars[0] = (WCHAR)(((key - 0x010000U) >> 10) + 0xD800); + chars[1] = (WCHAR)(((key - 0x010000U) & 0x3FF) + 0xDC00); + } else { + chars[0] = (WCHAR)(key & 0xFFFF); + } + + WORD glyphs[2] = { 0, 0 }; + GetGlyphIndicesW(this->dc, chars, key >= 0x010000U ? 2 : 1, glyphs, GGI_MARK_NONEXISTING_GLYPHS); + + return glyphs[0] != 0xFFFF ? glyphs[0] : 0; +} + +/* virtual */ const void *Win32FontCache::InternalGetFontTable(uint32 tag, size_t &length) +{ + DWORD len = GetFontData(this->dc, tag, 0, nullptr, 0); + + void *result = nullptr; + if (len != GDI_ERROR && len > 0) { + result = MallocT(len); + GetFontData(this->dc, tag, 0, result, len); + } + + length = len; + return result; +} + + +/** + * Loads the GDI font. + * If a GDI font description is present, e.g. from the automatic font + * fallback search, use it. Otherwise, try to resolve it by font name. + * @param fs The font size to load. + */ +void LoadWin32Font(FontSize fs) +{ + static const char *SIZE_TO_NAME[] = { "medium", "small", "large", "mono" }; + + FreeTypeSubSetting *settings = nullptr; + switch (fs) { + case FS_SMALL: settings = &_freetype.small; break; + case FS_NORMAL: settings = &_freetype.medium; break; + case FS_LARGE: settings = &_freetype.large; break; + case FS_MONO: settings = &_freetype.mono; break; + default: NOT_REACHED(); + } + + if (StrEmpty(settings->font)) return; + + LOGFONT logfont; + MemSetT(&logfont, 0); + logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH; + logfont.lfCharSet = DEFAULT_CHARSET; + logfont.lfOutPrecision = OUT_OUTLINE_PRECIS; + logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + + if (settings->os_handle != nullptr) { + logfont = *(const LOGFONT *)settings->os_handle; + } else if (strchr(settings->font, '.') != nullptr) { + /* Might be a font file name, try load it. */ + + TCHAR fontPath[MAX_PATH] = {}; + + /* See if this is an absolute path. */ + if (FileExists(settings->font)) { + convert_to_fs(settings->font, fontPath, lengthof(fontPath), false); + } else { + /* Scan the search-paths to see if it can be found. */ + std::string full_font = FioFindFullPath(BASE_DIR, settings->font); + if (!full_font.empty()) { + convert_to_fs(full_font.c_str(), fontPath, lengthof(fontPath), false); + } + } + + if (fontPath[0] != 0) { + if (AddFontResourceEx(fontPath, FR_PRIVATE, 0) != 0) { + /* Try a nice little undocumented function first for getting the internal font name. + * Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */ + typedef BOOL(WINAPI *PFNGETFONTRESOURCEINFO)(LPCTSTR, LPDWORD, LPVOID, DWORD); +#ifdef UNICODE + static PFNGETFONTRESOURCEINFO GetFontResourceInfo = (PFNGETFONTRESOURCEINFO)GetProcAddress(GetModuleHandle(_T("Gdi32")), "GetFontResourceInfoW"); +#else + static PFNGETFONTRESOURCEINFO GetFontResourceInfo = (PFNGETFONTRESOURCEINFO)GetProcAddress(GetModuleHandle(_T("Gdi32")), "GetFontResourceInfoA"); +#endif + + if (GetFontResourceInfo != nullptr) { + /* Try to query an array of LOGFONTs that describe the file. */ + DWORD len = 0; + if (GetFontResourceInfo(fontPath, &len, nullptr, 2) && len >= sizeof(LOGFONT)) { + LOGFONT *buf = (LOGFONT *)AllocaM(byte, len); + if (GetFontResourceInfo(fontPath, &len, buf, 2)) { + logfont = *buf; // Just use first entry. + } + } + } + + /* No dice yet. Use the file name as the font face name, hoping it matches. */ + if (logfont.lfFaceName[0] == 0) { + TCHAR fname[_MAX_FNAME]; + _tsplitpath(fontPath, nullptr, nullptr, fname, nullptr); + + _tcsncpy_s(logfont.lfFaceName, lengthof(logfont.lfFaceName), fname, _TRUNCATE); + logfont.lfWeight = strcasestr(settings->font, " bold") != nullptr || strcasestr(settings->font, "-bold") != nullptr ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. + } + } else { + ShowInfoF("Unable to load file '%s' for %s font, using default windows font selection instead", settings->font, SIZE_TO_NAME[fs]); + } + } + } + + if (logfont.lfFaceName[0] == 0) { + logfont.lfWeight = strcasestr(settings->font, " bold") != nullptr ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. + convert_to_fs(settings->font, logfont.lfFaceName, lengthof(logfont.lfFaceName), false); + } + + HFONT font = CreateFontIndirect(&logfont); + if (font == nullptr) { + ShowInfoF("Unable to use '%s' for %s font, Win32 reported error 0x%lX, using sprite font instead", settings->font, SIZE_TO_NAME[fs], GetLastError()); + return; + } + DeleteObject(font); + + new Win32FontCache(fs, logfont, settings->size); +} diff --git a/src/os/windows/font_win32.h b/src/os/windows/font_win32.h new file mode 100644 index 0000000000..9a552125fd --- /dev/null +++ b/src/os/windows/font_win32.h @@ -0,0 +1,42 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** @file font_win32.h Functions related to font handling on Win32. */ + +#ifndef FONT_WIN32_H +#define FONT_WIN32_H + +#include "../../fontcache_internal.h" +#include "win32.h" + +/** Font cache for fonts that are based on a Win32 font. */ +class Win32FontCache : public TrueTypeFontCache { +private: + LOGFONT logfont; ///< Logical font information for selecting the font face. + HFONT font = nullptr; ///< The font face associated with this font. + HDC dc = nullptr; ///< Cached GDI device context. + HGDIOBJ old_font; ///< Old font selected into the GDI context. + SIZE glyph_size; ///< Maximum size of regular glyphs. + + void SetFontSize(FontSize fs, int pixels); + +protected: + const void *InternalGetFontTable(uint32 tag, size_t &length) override; + const Sprite *InternalGetGlyph(GlyphID key, bool aa) override; + +public: + Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels); + ~Win32FontCache(); + void ClearFontCache() override; + GlyphID MapCharToGlyph(WChar key) override; + const char *GetFontName() override { return WIDE_TO_MB(this->logfont.lfFaceName); } + const void *GetOSHandle() override { return &this->logfont; } +}; + +void LoadWin32Font(FontSize fs); + +#endif /* FONT_WIN32_H */ From f2294851e6b468ba49b108a26e75b98a63a7c05c Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 13 Feb 2021 17:46:05 +0100 Subject: [PATCH 041/122] Codechange: [OSX] Move OSX-specific font code to a separate file. --- src/fontdetection.cpp | 121 +----------------------------- src/os/macosx/CMakeLists.txt | 1 + src/os/macosx/font_osx.cpp | 138 +++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 120 deletions(-) create mode 100644 src/os/macosx/font_osx.cpp diff --git a/src/fontdetection.cpp b/src/fontdetection.cpp index ca9f620b9b..50bd274ed5 100644 --- a/src/fontdetection.cpp +++ b/src/fontdetection.cpp @@ -24,126 +24,7 @@ extern FT_Library _library; * If no appropriate font is found, the function returns an error */ -#if defined(__APPLE__) -/* ======================================================================================== - * OSX support - * ======================================================================================== */ - -#include "os/macosx/macos.h" - -#include "safeguards.h" - -FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) -{ - FT_Error err = FT_Err_Cannot_Open_Resource; - - /* Get font reference from name. */ - UInt8 file_path[PATH_MAX]; - OSStatus os_err = -1; - CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, font_name, kCFStringEncodingUTF8)); - - /* Simply creating the font using CTFontCreateWithNameAndSize will *always* return - * something, no matter the name. As such, we can't use it to check for existence. - * We instead query the list of all font descriptors that match the given name which - * does not do this stupid name fallback. */ - CFAutoRelease name_desc(CTFontDescriptorCreateWithNameAndSize(name.get(), 0.0)); - CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontNameAttribute)), 1, &kCFTypeSetCallBacks)); - CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(name_desc.get(), mandatory_attribs.get())); - - /* Loop over all matches until we can get a path for one of them. */ - for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()) && os_err != noErr; i++) { - CFAutoRelease font(CTFontCreateWithFontDescriptor((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i), 0.0, nullptr)); - CFAutoRelease fontURL((CFURLRef)CTFontCopyAttribute(font.get(), kCTFontURLAttribute)); - if (CFURLGetFileSystemRepresentation(fontURL.get(), true, file_path, lengthof(file_path))) os_err = noErr; - } - - if (os_err == noErr) { - DEBUG(freetype, 3, "Font path for %s: %s", font_name, file_path); - err = FT_New_Face(_library, (const char *)file_path, 0, face); - } - - return err; -} - -bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) -{ - /* Determine fallback font using CoreText. This uses the language isocode - * to find a suitable font. CoreText is available from 10.5 onwards. */ - char lang[16]; - if (strcmp(language_isocode, "zh_TW") == 0) { - /* Traditional Chinese */ - strecpy(lang, "zh-Hant", lastof(lang)); - } else if (strcmp(language_isocode, "zh_CN") == 0) { - /* Simplified Chinese */ - strecpy(lang, "zh-Hans", lastof(lang)); - } else { - /* Just copy the first part of the isocode. */ - strecpy(lang, language_isocode, lastof(lang)); - char *sep = strchr(lang, '_'); - if (sep != nullptr) *sep = '\0'; - } - - /* Create a font descriptor matching the wanted language and latin (english) glyphs. - * Can't use CFAutoRelease here for everything due to the way the dictionary has to be created. */ - CFStringRef lang_codes[2]; - lang_codes[0] = CFStringCreateWithCString(kCFAllocatorDefault, lang, kCFStringEncodingUTF8); - lang_codes[1] = CFSTR("en"); - CFArrayRef lang_arr = CFArrayCreate(kCFAllocatorDefault, (const void **)lang_codes, lengthof(lang_codes), &kCFTypeArrayCallBacks); - CFAutoRelease lang_attribs(CFDictionaryCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontLanguagesAttribute)), (const void **)&lang_arr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); - CFAutoRelease lang_desc(CTFontDescriptorCreateWithAttributes(lang_attribs.get())); - CFRelease(lang_arr); - CFRelease(lang_codes[0]); - - /* Get array of all font descriptors for the wanted language. */ - CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontLanguagesAttribute)), 1, &kCFTypeSetCallBacks)); - CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(lang_desc.get(), mandatory_attribs.get())); - - bool result = false; - for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()); i++) { - CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i); - - /* Get font traits. */ - CFAutoRelease traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute)); - CTFontSymbolicTraits symbolic_traits; - CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait), kCFNumberIntType, &symbolic_traits); - - /* Skip symbol fonts and vertical fonts. */ - if ((symbolic_traits & kCTFontClassMaskTrait) == (CTFontStylisticClass)kCTFontSymbolicClass || (symbolic_traits & kCTFontVerticalTrait)) continue; - /* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */ - if (symbolic_traits & kCTFontBoldTrait) continue; - /* Select monospaced fonts if asked for. */ - if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue; - - /* Get font name. */ - char name[128]; - CFAutoRelease font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute)); - CFStringGetCString(font_name.get(), name, lengthof(name), kCFStringEncodingUTF8); - - /* There are some special fonts starting with an '.' and the last - * resort font that aren't usable. Skip them. */ - if (name[0] == '.' || strncmp(name, "LastResort", 10) == 0) continue; - - /* Save result. */ - callback->SetFontNames(settings, name); - if (!callback->FindMissingGlyphs()) { - DEBUG(freetype, 2, "CT-Font for %s: %s", language_isocode, name); - result = true; - break; - } - } - - if (!result) { - /* For some OS versions, the font 'Arial Unicode MS' does not report all languages it - * supports. If we didn't find any other font, just try it, maybe we get lucky. */ - callback->SetFontNames(settings, "Arial Unicode MS"); - result = !callback->FindMissingGlyphs(); - } - - callback->FindMissingGlyphs(); - return result; -} - -#elif defined(WITH_FONTCONFIG) /* end ifdef __APPLE__ */ +#if defined(WITH_FONTCONFIG) /* end ifdef __APPLE__ */ #include diff --git a/src/os/macosx/CMakeLists.txt b/src/os/macosx/CMakeLists.txt index e6b6c237b0..31f6af7d49 100644 --- a/src/os/macosx/CMakeLists.txt +++ b/src/os/macosx/CMakeLists.txt @@ -1,5 +1,6 @@ add_files( crashlog_osx.cpp + font_osx.cpp macos.h macos.mm osx_stdafx.h diff --git a/src/os/macosx/font_osx.cpp b/src/os/macosx/font_osx.cpp new file mode 100644 index 0000000000..f9436312b5 --- /dev/null +++ b/src/os/macosx/font_osx.cpp @@ -0,0 +1,138 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** @file font_osx.cpp Functions related to font handling on MacOS. */ + +#include "../../stdafx.h" +#include "../../debug.h" +#include "../../fontdetection.h" +#include "../../string_func.h" +#include "../../strings_func.h" +#include "macos.h" + +#include "safeguards.h" + +#ifdef WITH_FREETYPE + +#include +#include FT_FREETYPE_H + +extern FT_Library _library; + + +FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) +{ + FT_Error err = FT_Err_Cannot_Open_Resource; + + /* Get font reference from name. */ + UInt8 file_path[PATH_MAX]; + OSStatus os_err = -1; + CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, font_name, kCFStringEncodingUTF8)); + + /* Simply creating the font using CTFontCreateWithNameAndSize will *always* return + * something, no matter the name. As such, we can't use it to check for existence. + * We instead query the list of all font descriptors that match the given name which + * does not do this stupid name fallback. */ + CFAutoRelease name_desc(CTFontDescriptorCreateWithNameAndSize(name.get(), 0.0)); + CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontNameAttribute)), 1, &kCFTypeSetCallBacks)); + CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(name_desc.get(), mandatory_attribs.get())); + + /* Loop over all matches until we can get a path for one of them. */ + for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()) && os_err != noErr; i++) { + CFAutoRelease font(CTFontCreateWithFontDescriptor((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i), 0.0, nullptr)); + CFAutoRelease fontURL((CFURLRef)CTFontCopyAttribute(font.get(), kCTFontURLAttribute)); + if (CFURLGetFileSystemRepresentation(fontURL.get(), true, file_path, lengthof(file_path))) os_err = noErr; + } + + if (os_err == noErr) { + DEBUG(freetype, 3, "Font path for %s: %s", font_name, file_path); + err = FT_New_Face(_library, (const char *)file_path, 0, face); + } + + return err; +} + +#endif /* WITH_FREETYPE */ + + +bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) +{ + /* Determine fallback font using CoreText. This uses the language isocode + * to find a suitable font. CoreText is available from 10.5 onwards. */ + char lang[16]; + if (strcmp(language_isocode, "zh_TW") == 0) { + /* Traditional Chinese */ + strecpy(lang, "zh-Hant", lastof(lang)); + } else if (strcmp(language_isocode, "zh_CN") == 0) { + /* Simplified Chinese */ + strecpy(lang, "zh-Hans", lastof(lang)); + } else { + /* Just copy the first part of the isocode. */ + strecpy(lang, language_isocode, lastof(lang)); + char *sep = strchr(lang, '_'); + if (sep != nullptr) *sep = '\0'; + } + + /* Create a font descriptor matching the wanted language and latin (english) glyphs. + * Can't use CFAutoRelease here for everything due to the way the dictionary has to be created. */ + CFStringRef lang_codes[2]; + lang_codes[0] = CFStringCreateWithCString(kCFAllocatorDefault, lang, kCFStringEncodingUTF8); + lang_codes[1] = CFSTR("en"); + CFArrayRef lang_arr = CFArrayCreate(kCFAllocatorDefault, (const void **)lang_codes, lengthof(lang_codes), &kCFTypeArrayCallBacks); + CFAutoRelease lang_attribs(CFDictionaryCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontLanguagesAttribute)), (const void **)&lang_arr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + CFAutoRelease lang_desc(CTFontDescriptorCreateWithAttributes(lang_attribs.get())); + CFRelease(lang_arr); + CFRelease(lang_codes[0]); + + /* Get array of all font descriptors for the wanted language. */ + CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontLanguagesAttribute)), 1, &kCFTypeSetCallBacks)); + CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(lang_desc.get(), mandatory_attribs.get())); + + bool result = false; + for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()); i++) { + CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i); + + /* Get font traits. */ + CFAutoRelease traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute)); + CTFontSymbolicTraits symbolic_traits; + CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait), kCFNumberIntType, &symbolic_traits); + + /* Skip symbol fonts and vertical fonts. */ + if ((symbolic_traits & kCTFontClassMaskTrait) == (CTFontStylisticClass)kCTFontSymbolicClass || (symbolic_traits & kCTFontVerticalTrait)) continue; + /* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */ + if (symbolic_traits & kCTFontBoldTrait) continue; + /* Select monospaced fonts if asked for. */ + if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue; + + /* Get font name. */ + char name[128]; + CFAutoRelease font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute)); + CFStringGetCString(font_name.get(), name, lengthof(name), kCFStringEncodingUTF8); + + /* There are some special fonts starting with an '.' and the last + * resort font that aren't usable. Skip them. */ + if (name[0] == '.' || strncmp(name, "LastResort", 10) == 0) continue; + + /* Save result. */ + callback->SetFontNames(settings, name); + if (!callback->FindMissingGlyphs()) { + DEBUG(freetype, 2, "CT-Font for %s: %s", language_isocode, name); + result = true; + break; + } + } + + if (!result) { + /* For some OS versions, the font 'Arial Unicode MS' does not report all languages it + * supports. If we didn't find any other font, just try it, maybe we get lucky. */ + callback->SetFontNames(settings, "Arial Unicode MS"); + result = !callback->FindMissingGlyphs(); + } + + callback->FindMissingGlyphs(); + return result; +} From 63ed3f3575ade286782f56c249bcd2478e656adc Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 13 Feb 2021 17:53:41 +0100 Subject: [PATCH 042/122] Codechange: Move Fontconfig-specific code to a seperate file. --- src/CMakeLists.txt | 1 - src/fontcache.cpp | 15 +++-- src/os/unix/CMakeLists.txt | 5 ++ .../unix/font_unix.cpp} | 67 +++++++------------ 4 files changed, 39 insertions(+), 49 deletions(-) rename src/{fontdetection.cpp => os/unix/font_unix.cpp} (71%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d451ae8da6..e0af799994 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -156,7 +156,6 @@ add_files( fontcache.cpp fontcache.h fontcache_internal.h - fontdetection.cpp fontdetection.h framerate_gui.cpp framerate_type.h diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 6cc25154f8..6a0b500d86 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -205,7 +205,6 @@ bool SpriteFontCache::GetDrawGlyphShadow() /* static */ FontCache *FontCache::caches[FS_END] = { new SpriteFontCache(FS_NORMAL), new SpriteFontCache(FS_SMALL), new SpriteFontCache(FS_LARGE), new SpriteFontCache(FS_MONO) }; -#if defined(WITH_FREETYPE) || defined(_WIN32) /** * Create a new TrueTypeFontCache. @@ -678,13 +677,8 @@ const void *FreeTypeFontCache::InternalGetFontTable(uint32 tag, size_t &length) return result; } -#elif defined(_WIN32) - - - #endif /* WITH_FREETYPE */ -#endif /* defined(WITH_FREETYPE) || defined(_WIN32) */ /** * (Re)initialize the freetype related things, i.e. load the non-sprite fonts. @@ -722,3 +716,12 @@ void UninitFreeType() _library = nullptr; #endif /* WITH_FREETYPE */ } + +#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) + +#ifdef WITH_FREETYPE +FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) { return FT_Err_Cannot_Open_Resource; } +#endif /* WITH_FREETYPE */ + +bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) { return false; } +#endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) */ diff --git a/src/os/unix/CMakeLists.txt b/src/os/unix/CMakeLists.txt index b548d3bb21..8e74f96643 100644 --- a/src/os/unix/CMakeLists.txt +++ b/src/os/unix/CMakeLists.txt @@ -7,3 +7,8 @@ add_files( unix.cpp CONDITION UNIX AND NOT OPTION_OS2 ) + +add_files( + font_unix.cpp + CONDITION Fontconfig_FOUND +) diff --git a/src/fontdetection.cpp b/src/os/unix/font_unix.cpp similarity index 71% rename from src/fontdetection.cpp rename to src/os/unix/font_unix.cpp index 50bd274ed5..b607db8f8a 100644 --- a/src/fontdetection.cpp +++ b/src/os/unix/font_unix.cpp @@ -5,34 +5,26 @@ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . */ -/** @file fontdetection.cpp Detection of the right font. */ +/** @file font_unix.cpp Functions related to font handling on Unix/Fontconfig. */ -#if defined(WITH_FREETYPE) || defined(_WIN32) +#include "../../stdafx.h" +#include "../../debug.h" +#include "../../fontdetection.h" +#include "../../string_func.h" +#include "../../strings_func.h" -#include "stdafx.h" -#include "debug.h" -#include "fontdetection.h" -#include "string_func.h" -#include "strings_func.h" +#include -#ifdef WITH_FREETYPE -extern FT_Library _library; -#endif /* WITH_FREETYPE */ +#include "safeguards.h" -/** - * Get the font loaded into a Freetype face by using a font-name. - * If no appropriate font is found, the function returns an error - */ +#ifdef WITH_FREETYPE -#if defined(WITH_FONTCONFIG) /* end ifdef __APPLE__ */ +#include +#include FT_FREETYPE_H -#include +extern FT_Library _library; -#include "safeguards.h" -/* ======================================================================================== - * FontConfig (unix) support - * ======================================================================================== */ FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) { FT_Error err = FT_Err_Cannot_Open_Resource; @@ -57,8 +49,8 @@ FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) } /* Resolve the name and populate the information structure */ - pat = FcNameParse((FcChar8*)font_family); - if (font_style != nullptr) FcPatternAddString(pat, FC_STYLE, (FcChar8*)font_style); + pat = FcNameParse((FcChar8 *)font_family); + if (font_style != nullptr) FcPatternAddString(pat, FC_STYLE, (FcChar8 *)font_style); FcConfigSubstitute(0, pat, FcMatchPattern); FcDefaultSubstitute(pat); fs = FcFontSetCreate(); @@ -73,17 +65,17 @@ FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) for (i = 0; err != FT_Err_Ok && i < fs->nfont; i++) { /* Try the new filename */ - if (FcPatternGetString(fs->fonts[i], FC_FILE, 0, &file) == FcResultMatch && - FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) == FcResultMatch && - FcPatternGetString(fs->fonts[i], FC_STYLE, 0, &style) == FcResultMatch) { + if (FcPatternGetString(fs->fonts[i], FC_FILE, 0, &file) == FcResultMatch && + FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) == FcResultMatch && + FcPatternGetString(fs->fonts[i], FC_STYLE, 0, &style) == FcResultMatch) { /* The correct style? */ - if (font_style != nullptr && strcasecmp(font_style, (char*)style) != 0) continue; + if (font_style != nullptr && strcasecmp(font_style, (char *)style) != 0) continue; /* Font config takes the best shot, which, if the family name is spelled * wrongly a 'random' font, so check whether the family name is the * same as the supplied name */ - if (strcasecmp(font_family, (char*)family) == 0) { + if (strcasecmp(font_family, (char *)family) == 0) { err = FT_New_Face(_library, (char *)file, 0, face); } } @@ -99,6 +91,9 @@ FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) return err; } +#endif /* WITH_FREETYPE */ + + bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) { if (!FcInit()) return false; @@ -114,7 +109,7 @@ bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, i if (split != nullptr) *split = '\0'; /* First create a pattern to match the wanted language. */ - FcPattern *pat = FcNameParse((FcChar8*)lang); + FcPattern *pat = FcNameParse((FcChar8 *)lang); /* We only want to know the filename. */ FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_SPACING, FC_SLANT, FC_WEIGHT, nullptr); /* Get the list of filenames matching the wanted language. */ @@ -150,14 +145,14 @@ bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, i FcPatternGetInteger(font, FC_WEIGHT, 0, &value); if (value <= best_weight) continue; - callback->SetFontNames(settings, (const char*)file); + callback->SetFontNames(settings, (const char *)file); bool missing = callback->FindMissingGlyphs(); DEBUG(freetype, 1, "Font \"%s\" misses%s glyphs", file, missing ? "" : " no"); if (!missing) { best_weight = value; - best_font = (const char *)file; + best_font = (const char *)file; } } @@ -174,15 +169,3 @@ bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, i FcFini(); return ret; } -#endif /* end ifdef WITH_FONTCONFIG */ - -#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) - -#ifdef WITH_FREETYPE -FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) {return FT_Err_Cannot_Open_Resource;} -#endif /* WITH_FREETYPE */ - -bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) { return false; } -#endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) */ - -#endif /* WITH_FREETYPE */ From 4db7837d0671c228303c76f5979bbeb89c600634 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 9 Jan 2021 18:43:12 +0100 Subject: [PATCH 043/122] Codechange: [OSX] There is only one subdriver left, remove virtual dispatch. --- src/video/cocoa/cocoa_v.h | 76 +++++++++++++------------- src/video/cocoa/cocoa_v.mm | 100 ++++------------------------------- src/video/cocoa/cocoa_wnd.h | 14 ++--- src/video/cocoa/cocoa_wnd.mm | 6 +-- 4 files changed, 58 insertions(+), 138 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index 54ff53d156..2aca2034eb 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -86,12 +86,22 @@ public: }; -/** - * Generic display driver for cocoa - * On grounds to not duplicate some code, it contains a few variables - * which are not used by all device drivers. - */ -class CocoaSubdriver { + +class WindowQuartzSubdriver { +private: + /** + * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode. + * + * @param left The x coord for the left edge of the box to blit. + * @param top The y coord for the top edge of the box to blit. + * @param right The x coord for the right edge of the box to blit. + * @param bottom The y coord for the bottom edge of the box to blit. + */ + void BlitIndexedToView32(int left, int top, int right, int bottom); + + void GetDeviceInfo(); + bool SetVideoMode(int width, int height, int bpp); + public: int device_width; ///< Width of device in pixel int device_height; ///< Height of device in pixel @@ -104,8 +114,6 @@ public: int buffer_depth; ///< Colour depth of used frame buffer void *pixel_buffer; ///< used for direct pixel access void *window_buffer; ///< Colour translation from palette to screen - CGColorSpaceRef color_space; //< Window color space - id window; ///< Pointer to window object # define MAX_DIRTY_RECTS 100 Rect dirty_rects[MAX_DIRTY_RECTS]; ///< dirty rectangles @@ -115,20 +123,18 @@ public: bool active; ///< Whether the window is visible bool setup; + id window; ///< Pointer to window object id cocoaview; ///< Pointer to view object + CGColorSpaceRef color_space; ///< Window color space + CGContextRef cgcontext; ///< Context reference for Quartz subdriver - /* Separate driver vars for Quarz - * Needed here in order to avoid much code duplication */ - CGContextRef cgcontext; ///< Context reference for Quartz subdriver - - /* Driver methods */ - /** Initialize driver */ - virtual ~CocoaSubdriver() {} + WindowQuartzSubdriver(); + ~WindowQuartzSubdriver(); /** Draw window * @param force_update Whether to redraw unconditionally */ - virtual void Draw(bool force_update = false) = 0; + void Draw(bool force_update = false); /** Mark dirty a screen region * @param left x-coordinate of left border @@ -136,77 +142,73 @@ public: * @param width width or dirty rectangle * @param height height of dirty rectangle */ - virtual void MakeDirty(int left, int top, int width, int height) = 0; + void MakeDirty(int left, int top, int width, int height); /** Update the palette */ - virtual void UpdatePalette(uint first_color, uint num_colors) = 0; + void UpdatePalette(uint first_color, uint num_colors); - virtual uint ListModes(OTTD_Point *modes, uint max_modes) = 0; + uint ListModes(OTTD_Point *modes, uint max_modes); /** Change window resolution * @param w New window width * @param h New window height * @return Whether change was successful */ - virtual bool ChangeResolution(int w, int h, int bpp) = 0; + bool ChangeResolution(int w, int h, int bpp); /** Are we in fullscreen mode * @return whether fullscreen mode is currently used */ - virtual bool IsFullscreen() = 0; + bool IsFullscreen(); /** Toggle between fullscreen and windowed mode * @return whether switch was successful */ - virtual bool ToggleFullscreen(bool fullscreen) { return false; }; + bool ToggleFullscreen(bool fullscreen); /** Return the width of the current view * @return width of the current view */ - virtual int GetWidth() = 0; + int GetWidth() { return window_width; } /** Return the height of the current view * @return height of the current view */ - virtual int GetHeight() = 0; + int GetHeight() { return window_height; } /** Return the current pixel buffer * @return pixelbuffer */ - virtual void *GetPixelBuffer() = 0; + void *GetPixelBuffer() { return buffer_depth == 8 ? pixel_buffer : window_buffer; } /** Convert local coordinate to window server (CoreGraphics) coordinate * @param p local coordinates * @return window driver coordinates */ - virtual CGPoint PrivateLocalToCG(NSPoint *p) = 0; + CGPoint PrivateLocalToCG(NSPoint *p); /** Return the mouse location * @param event UI event * @return mouse location as NSPoint */ - virtual NSPoint GetMouseLocation(NSEvent *event) = 0; + NSPoint GetMouseLocation(NSEvent *event); /** Return whether the mouse is within our view * @param pt Mouse coordinates * @return Whether mouse coordinates are within view */ - virtual bool MouseIsInsideView(NSPoint *pt) = 0; + bool MouseIsInsideView(NSPoint *pt); - /** Return whether the window is active (visible) - * @return whether the window is visible or not - */ - virtual bool IsActive() = 0; + /** Return whether the window is active (visible) */ + bool IsActive() { return active; } - /** Whether the window was successfully resized + /** Resize the window. * @return whether the window was successfully resized */ - virtual bool WindowResized() { return false; }; + bool WindowResized(); }; -extern CocoaSubdriver *_cocoa_subdriver; - -CocoaSubdriver *QZ_CreateWindowQuartzSubdriver(int width, int height, int bpp); +extern WindowQuartzSubdriver *_cocoa_subdriver; uint QZ_ListModes(OTTD_Point *modes, uint max_modes, CGDirectDisplayID display_id, int display_depth); diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index f09920fe20..5da0f55e31 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -56,7 +56,7 @@ #endif bool _cocoa_video_started = false; -CocoaSubdriver *_cocoa_subdriver = NULL; +WindowQuartzSubdriver *_cocoa_subdriver = NULL; static bool ModeSorter(const OTTD_Point &p1, const OTTD_Point &p2) @@ -143,32 +143,6 @@ static void QZ_UpdateVideoModes() } } -/** - * Find a suitable cocoa subdriver. - * - * @param width Width of display area. - * @param height Height of display area. - * @param bpp Colour depth of display area. - * @param fullscreen Whether a fullscreen mode is requested. - * @param fallback Whether we look for a fallback driver. - * @return Pointer to window subdriver. - */ -static CocoaSubdriver *QZ_CreateSubdriver(int width, int height, int bpp, bool fullscreen, bool fallback) -{ - CocoaSubdriver *ret = QZ_CreateWindowQuartzSubdriver(width, height, bpp); - if (ret != nullptr && fullscreen) ret->ToggleFullscreen(fullscreen); - - if (ret != nullptr) return ret; - if (!fallback) return nullptr; - - /* Try again in 640x480 windowed */ - DEBUG(driver, 0, "Setting video mode failed, falling back to 640x480 windowed mode."); - ret = QZ_CreateWindowQuartzSubdriver(640, 480, bpp); - if (ret != nullptr) return ret; - - return nullptr; -} - static FVideoDriver_Cocoa iFVideoDriver_Cocoa; @@ -182,7 +156,7 @@ void VideoDriver_Cocoa::Stop() CocoaExitApplication(); delete _cocoa_subdriver; - _cocoa_subdriver = NULL; + _cocoa_subdriver = nullptr; _cocoa_video_started = false; } @@ -198,7 +172,7 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm) _cocoa_video_started = true; /* Don't create a window or enter fullscreen if we're just going to show a dialog. */ - if (!CocoaSetupApplication()) return NULL; + if (!CocoaSetupApplication()) return nullptr; this->UpdateAutoResolution(); @@ -212,12 +186,14 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm) return "The cocoa quartz subdriver only supports 8 and 32 bpp."; } - _cocoa_subdriver = QZ_CreateSubdriver(width, height, bpp, _fullscreen, true); - if (_cocoa_subdriver == NULL) { + _cocoa_subdriver = new WindowQuartzSubdriver(); + if (!_cocoa_subdriver->ChangeResolution(width, height, bpp)) { Stop(); return "Could not create subdriver"; } + if (_fullscreen) _cocoa_subdriver->ToggleFullscreen(_fullscreen); + this->GameSizeChanged(); QZ_UpdateVideoModes(); @@ -340,59 +316,14 @@ class WindowQuartzSubdriver; /* Subclass of OTTD_CocoaView to fix Quartz rendering */ @interface OTTD_QuartzView : OTTD_CocoaView -- (void)setDriver:(WindowQuartzSubdriver*)drv; +- (void)setDriver:(WindowQuartzSubdriver *)drv; - (void)drawRect:(NSRect)invalidRect; @end -class WindowQuartzSubdriver : public CocoaSubdriver { -private: - /** - * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode. - * - * @param left The x coord for the left edge of the box to blit. - * @param top The y coord for the top edge of the box to blit. - * @param right The x coord for the right edge of the box to blit. - * @param bottom The y coord for the bottom edge of the box to blit. - */ - void BlitIndexedToView32(int left, int top, int right, int bottom); - - virtual void GetDeviceInfo(); - virtual bool SetVideoMode(int width, int height, int bpp); - -public: - WindowQuartzSubdriver(); - virtual ~WindowQuartzSubdriver(); - - virtual void Draw(bool force_update); - virtual void MakeDirty(int left, int top, int width, int height); - virtual void UpdatePalette(uint first_color, uint num_colors); - - virtual uint ListModes(OTTD_Point *modes, uint max_modes); - - virtual bool ChangeResolution(int w, int h, int bpp); - - virtual bool IsFullscreen(); - virtual bool ToggleFullscreen(bool fullscreen); /* Full screen mode on OSX 10.7 */ - - virtual int GetWidth() { return window_width; } - virtual int GetHeight() { return window_height; } - virtual void *GetPixelBuffer() { return buffer_depth == 8 ? pixel_buffer : window_buffer; } - - /* Convert local coordinate to window server (CoreGraphics) coordinate */ - virtual CGPoint PrivateLocalToCG(NSPoint *p); - - virtual NSPoint GetMouseLocation(NSEvent *event); - virtual bool MouseIsInsideView(NSPoint *pt); - - virtual bool IsActive() { return active; } - - bool WindowResized(); -}; - @implementation OTTD_QuartzView -- (void)setDriver:(WindowQuartzSubdriver*)drv +- (void)setDriver:(WindowQuartzSubdriver *)drv { driver = drv; } @@ -850,17 +781,4 @@ bool WindowQuartzSubdriver::WindowResized() return true; } - -CocoaSubdriver *QZ_CreateWindowQuartzSubdriver(int width, int height, int bpp) -{ - WindowQuartzSubdriver *ret = new WindowQuartzSubdriver(); - - if (!ret->ChangeResolution(width, height, bpp)) { - delete ret; - return NULL; - } - - return ret; -} - #endif /* WITH_COCOA */ diff --git a/src/video/cocoa/cocoa_wnd.h b/src/video/cocoa/cocoa_wnd.h index f2890ec0f0..837532e9e0 100644 --- a/src/video/cocoa/cocoa_wnd.h +++ b/src/video/cocoa/cocoa_wnd.h @@ -12,7 +12,7 @@ #import -class CocoaSubdriver; +class WindowQuartzSubdriver; extern NSString *OTTDMainLaunchGameEngine; @@ -23,10 +23,10 @@ extern NSString *OTTDMainLaunchGameEngine; /** Subclass of NSWindow to cater our special needs */ @interface OTTD_CocoaWindow : NSWindow { - CocoaSubdriver *driver; + WindowQuartzSubdriver *driver; } -- (void)setDriver:(CocoaSubdriver*)drv; +- (void)setDriver:(WindowQuartzSubdriver *)drv; - (void)miniaturize:(id)sender; - (void)display; @@ -39,10 +39,10 @@ extern NSString *OTTDMainLaunchGameEngine; /** Subclass of NSView to fix Quartz rendering and mouse awareness */ @interface OTTD_CocoaView : NSView { - CocoaSubdriver *driver; + WindowQuartzSubdriver *driver; NSTrackingRectTag trackingtag; } -- (void)setDriver:(CocoaSubdriver*)drv; +- (void)setDriver:(WindowQuartzSubdriver *)drv; - (void)drawRect:(NSRect)rect; - (BOOL)isOpaque; - (BOOL)acceptsFirstResponder; @@ -59,10 +59,10 @@ extern NSString *OTTDMainLaunchGameEngine; /** Delegate for our NSWindow to send ask for quit on close */ @interface OTTD_CocoaWindowDelegate : NSObject { - CocoaSubdriver *driver; + WindowQuartzSubdriver *driver; } -- (void)setDriver:(CocoaSubdriver*)drv; +- (void)setDriver:(WindowQuartzSubdriver *)drv; - (BOOL)windowShouldClose:(id)sender; - (void)windowDidEnterFullScreen:(NSNotification *)aNotification; diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index 8c16dd1ef6..256565b2b8 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -282,7 +282,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel @implementation OTTD_CocoaWindow -- (void)setDriver:(CocoaSubdriver*)drv +- (void)setDriver:(WindowQuartzSubdriver *)drv { driver = drv; } @@ -404,7 +404,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) /** * Initialize the driver */ -- (void)setDriver:(CocoaSubdriver*)drv +- (void)setDriver:(WindowQuartzSubdriver *)drv { driver = drv; } @@ -810,7 +810,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) @implementation OTTD_CocoaWindowDelegate /** Initialize the video driver */ -- (void)setDriver:(CocoaSubdriver*)drv +- (void)setDriver:(WindowQuartzSubdriver *)drv { driver = drv; } From e6bea3961fa2245c6bc7bb268f9c9bffd4c22fff Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sun, 10 Jan 2021 22:27:31 +0100 Subject: [PATCH 044/122] Change: [OSX] Replace screen resolution list with suggested window sizes. We never change the real screen resolution on OSX. As such, offering a list of resolutions is pointless. Instead of that, offer the user a list of commonly used window sizes up to the current screen size. --- src/video/cocoa/cocoa_v.h | 3 +- src/video/cocoa/cocoa_v.mm | 117 +++++++++++-------------------------- 2 files changed, 34 insertions(+), 86 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index 2aca2034eb..a52cbb06e2 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -77,6 +77,7 @@ private: friend class WindowQuartzSubdriver; void GameSizeChanged(); + void UpdateVideoModes(); }; class FVideoDriver_Cocoa : public DriverFactoryBase { @@ -210,6 +211,4 @@ public: extern WindowQuartzSubdriver *_cocoa_subdriver; -uint QZ_ListModes(OTTD_Point *modes, uint max_modes, CGDirectDisplayID display_id, int display_depth); - #endif /* VIDEO_COCOA_H */ diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 5da0f55e31..77dbbd21f0 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -37,7 +37,6 @@ #include "../../core/math_func.hpp" #include "../../framerate_type.h" -#include #import /* for MAXPATHLEN */ /** @@ -59,87 +58,41 @@ bool _cocoa_video_started = false; WindowQuartzSubdriver *_cocoa_subdriver = NULL; -static bool ModeSorter(const OTTD_Point &p1, const OTTD_Point &p2) -{ - if (p1.x < p2.x) return true; - if (p1.x > p2.x) return false; - if (p1.y < p2.y) return true; - if (p1.y > p2.y) return false; - return false; -} - -static void QZ_GetDisplayModeInfo(CFArrayRef modes, CFIndex i, int &bpp, uint16 &width, uint16 &height) -{ - CGDisplayModeRef mode = static_cast(const_cast(CFArrayGetValueAtIndex(modes, i))); - - width = (uint16)CGDisplayModeGetWidth(mode); - height = (uint16)CGDisplayModeGetHeight(mode); - -#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11) - /* Extract bit depth from mode string. */ - CFAutoRelease pixEnc(CGDisplayModeCopyPixelEncoding(mode)); - if (CFStringCompare(pixEnc.get(), CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) bpp = 32; - if (CFStringCompare(pixEnc.get(), CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) bpp = 16; - if (CFStringCompare(pixEnc.get(), CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) bpp = 8; -#else - /* CGDisplayModeCopyPixelEncoding is deprecated on OSX 10.11+, but there are no 8 bpp modes anyway... */ - bpp = 32; -#endif -} - -uint QZ_ListModes(OTTD_Point *modes, uint max_modes, CGDirectDisplayID display_id, int device_depth) -{ - CFAutoRelease mode_list(CGDisplayCopyAllDisplayModes(display_id, nullptr)); - CFIndex num_modes = CFArrayGetCount(mode_list.get()); - - /* Build list of modes with the requested bpp */ - uint count = 0; - for (CFIndex i = 0; i < num_modes && count < max_modes; i++) { - int bpp; - uint16 width, height; - - QZ_GetDisplayModeInfo(mode_list.get(), i, bpp, width, height); - - if (bpp != device_depth) continue; - - /* Check if mode is already in the list */ - bool hasMode = false; - for (uint i = 0; i < count; i++) { - if (modes[i].x == width && modes[i].y == height) { - hasMode = true; - break; - } - } - - if (hasMode) continue; - - /* Add mode to the list */ - modes[count].x = width; - modes[count].y = height; - count++; - } - - /* Sort list smallest to largest */ - std::sort(modes, modes + count, ModeSorter); - - return count; -} +/** List of common display/window sizes. */ +static const Dimension _default_resolutions[] = { + { 640, 480 }, + { 800, 600 }, + { 1024, 768 }, + { 1152, 864 }, + { 1280, 800 }, + { 1280, 960 }, + { 1280, 1024 }, + { 1400, 1050 }, + { 1600, 1200 }, + { 1680, 1050 }, + { 1920, 1200 }, + { 2560, 1440 } +}; /** * Update the video modus. - * - * @pre _cocoa_subdriver != NULL */ -static void QZ_UpdateVideoModes() +void VideoDriver_Cocoa::UpdateVideoModes() { - assert(_cocoa_subdriver != NULL); - - OTTD_Point modes[32]; - uint count = _cocoa_subdriver->ListModes(modes, lengthof(modes)); - _resolutions.clear(); - for (uint i = 0; i < count; i++) { - _resolutions.emplace_back(modes[i].x, modes[i].y); + + if (_cocoa_subdriver != nullptr && _cocoa_subdriver->IsFullscreen()) { + /* Full screen, there is only one possible resolution. */ + NSSize screen = [ [ _cocoa_subdriver->window screen ] frame ].size; + _resolutions.emplace_back((uint)screen.width, (uint)screen.height); + } else { + /* Windowed; offer a selection of common window sizes up until the + * maximum usable screen space. This excludes the menu and dock areas. */ + NSSize maxSize = [ [ NSScreen mainScreen] visibleFrame ].size; + for (const auto &d : _default_resolutions) { + if (d.width < maxSize.width && d.height < maxSize.height) _resolutions.push_back(d); + } + _resolutions.emplace_back((uint)maxSize.width, (uint)maxSize.height); } } @@ -195,7 +148,7 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm) if (_fullscreen) _cocoa_subdriver->ToggleFullscreen(_fullscreen); this->GameSizeChanged(); - QZ_UpdateVideoModes(); + this->UpdateVideoModes(); return NULL; } @@ -242,7 +195,6 @@ bool VideoDriver_Cocoa::ChangeResolution(int w, int h) bool ret = _cocoa_subdriver->ChangeResolution(w, h, BlitterFactory::GetCurrentBlitter()->GetScreenDepth()); this->GameSizeChanged(); - QZ_UpdateVideoModes(); return ret; } @@ -257,7 +209,9 @@ bool VideoDriver_Cocoa::ToggleFullscreen(bool full_screen) { assert(_cocoa_subdriver != NULL); - return _cocoa_subdriver->ToggleFullscreen(full_screen); + bool res = _cocoa_subdriver->ToggleFullscreen(full_screen); + this->UpdateVideoModes(); + return res; } /** @@ -668,11 +622,6 @@ void WindowQuartzSubdriver::UpdatePalette(uint first_color, uint num_colors) this->num_dirty_rects = MAX_DIRTY_RECTS; } -uint WindowQuartzSubdriver::ListModes(OTTD_Point *modes, uint max_modes) -{ - return QZ_ListModes(modes, max_modes, kCGDirectMainDisplay, this->buffer_depth); -} - bool WindowQuartzSubdriver::ChangeResolution(int w, int h, int bpp) { int old_width = this->window_width; From 88f6c7a9f353c3e78f30ddeaaeef91694af4d189 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Fri, 22 Jan 2021 22:29:46 +0100 Subject: [PATCH 045/122] Codechange: [OSX] Fold remaining Cocoa video subdriver into the main driver. --- src/video/cocoa/cocoa_v.h | 128 ++++++++------------ src/video/cocoa/cocoa_v.mm | 229 +++++++++++++++++------------------ src/video/cocoa/cocoa_wnd.h | 14 +-- src/video/cocoa/cocoa_wnd.mm | 10 +- src/video/cocoa/event.mm | 102 ++++++---------- 5 files changed, 207 insertions(+), 276 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index a52cbb06e2..adeadd3bef 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -18,9 +18,37 @@ extern bool _cocoa_video_started; class VideoDriver_Cocoa : public VideoDriver { private: - Dimension orig_res; ///< Saved window size for non-fullscreen mode. + Dimension orig_res; ///< Saved window size for non-fullscreen mode. + + int device_width; ///< Width of device in pixel + int device_height; ///< Height of device in pixel + int device_depth; ///< Colour depth of device in bit + + int window_width; ///< Current window width in pixel + int window_height; ///< Current window height in pixel + int window_pitch; + + int buffer_depth; ///< Colour depth of used frame buffer + void *pixel_buffer; ///< used for direct pixel access + void *window_buffer; ///< Colour translation from palette to screen + +# define MAX_DIRTY_RECTS 100 + Rect dirty_rects[MAX_DIRTY_RECTS]; ///< dirty rectangles + int num_dirty_rects = MAX_DIRTY_RECTS; ///< Number of dirty rectangles + uint32 palette[256]; ///< Colour Palette public: + bool active; ///< Whether the window is visible + bool setup; + + id window; ///< Pointer to window object + id cocoaview; ///< Pointer to view object + CGColorSpaceRef color_space; ///< Window color space + CGContextRef cgcontext; ///< Context reference for Quartz subdriver + +public: + VideoDriver_Cocoa(); + const char *Start(const StringList ¶m) override; /** Stop the video driver */ @@ -70,26 +98,28 @@ public: /** Main game loop. */ void GameLoop(); // In event.mm. + /** Resize the window. + * @return whether the window was successfully resized + */ + bool WindowResized(); + + /** Convert local coordinate to window server (CoreGraphics) coordinate + * @param p local coordinates + * @return window driver coordinates + */ + CGPoint PrivateLocalToCG(NSPoint *p); + protected: Dimension GetScreenSize() const override; private: - friend class WindowQuartzSubdriver; + bool PollEvent(); // In event.mm. + void MouseMovedEvent(int x, int y); // In event.mm. + void WarpCursor(int x, int y); // In event.mm. void GameSizeChanged(); void UpdateVideoModes(); -}; -class FVideoDriver_Cocoa : public DriverFactoryBase { -public: - FVideoDriver_Cocoa() : DriverFactoryBase(Driver::DT_VIDEO, 10, "cocoa", "Cocoa Video Driver") {} - Driver *CreateInstance() const override { return new VideoDriver_Cocoa(); } -}; - - - -class WindowQuartzSubdriver { -private: /** * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode. * @@ -103,48 +133,11 @@ private: void GetDeviceInfo(); bool SetVideoMode(int width, int height, int bpp); -public: - int device_width; ///< Width of device in pixel - int device_height; ///< Height of device in pixel - int device_depth; ///< Colour depth of device in bit - - int window_width; ///< Current window width in pixel - int window_height; ///< Current window height in pixel - int window_pitch; - - int buffer_depth; ///< Colour depth of used frame buffer - void *pixel_buffer; ///< used for direct pixel access - void *window_buffer; ///< Colour translation from palette to screen - -# define MAX_DIRTY_RECTS 100 - Rect dirty_rects[MAX_DIRTY_RECTS]; ///< dirty rectangles - int num_dirty_rects; ///< Number of dirty rectangles - uint32 palette[256]; ///< Colour Palette - - bool active; ///< Whether the window is visible - bool setup; - - id window; ///< Pointer to window object - id cocoaview; ///< Pointer to view object - CGColorSpaceRef color_space; ///< Window color space - CGContextRef cgcontext; ///< Context reference for Quartz subdriver - - WindowQuartzSubdriver(); - ~WindowQuartzSubdriver(); - /** Draw window * @param force_update Whether to redraw unconditionally */ void Draw(bool force_update = false); - /** Mark dirty a screen region - * @param left x-coordinate of left border - * @param top y-coordinate of top border - * @param width width or dirty rectangle - * @param height height of dirty rectangle - */ - void MakeDirty(int left, int top, int width, int height); - /** Update the palette */ void UpdatePalette(uint first_color, uint num_colors); @@ -162,32 +155,11 @@ public: */ bool IsFullscreen(); - /** Toggle between fullscreen and windowed mode - * @return whether switch was successful - */ - bool ToggleFullscreen(bool fullscreen); - - /** Return the width of the current view - * @return width of the current view - */ - int GetWidth() { return window_width; } - - /** Return the height of the current view - * @return height of the current view - */ - int GetHeight() { return window_height; } - /** Return the current pixel buffer * @return pixelbuffer */ void *GetPixelBuffer() { return buffer_depth == 8 ? pixel_buffer : window_buffer; } - /** Convert local coordinate to window server (CoreGraphics) coordinate - * @param p local coordinates - * @return window driver coordinates - */ - CGPoint PrivateLocalToCG(NSPoint *p); - /** Return the mouse location * @param event UI event * @return mouse location as NSPoint @@ -200,15 +172,13 @@ public: */ bool MouseIsInsideView(NSPoint *pt); - /** Return whether the window is active (visible) */ - bool IsActive() { return active; } - - /** Resize the window. - * @return whether the window was successfully resized - */ - bool WindowResized(); + void CheckPaletteAnim(); }; -extern WindowQuartzSubdriver *_cocoa_subdriver; +class FVideoDriver_Cocoa : public DriverFactoryBase { +public: + FVideoDriver_Cocoa() : DriverFactoryBase(Driver::DT_VIDEO, 10, "cocoa", "Cocoa Video Driver") {} + Driver *CreateInstance() const override { return new VideoDriver_Cocoa(); } +}; #endif /* VIDEO_COCOA_H */ diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 77dbbd21f0..9d1fe37820 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -55,7 +55,6 @@ #endif bool _cocoa_video_started = false; -WindowQuartzSubdriver *_cocoa_subdriver = NULL; /** List of common display/window sizes. */ @@ -74,30 +73,27 @@ static const Dimension _default_resolutions[] = { { 2560, 1440 } }; -/** - * Update the video modus. - */ -void VideoDriver_Cocoa::UpdateVideoModes() +static FVideoDriver_Cocoa iFVideoDriver_Cocoa; + + +VideoDriver_Cocoa::VideoDriver_Cocoa() { - _resolutions.clear(); + this->window_width = 0; + this->window_height = 0; + this->buffer_depth = 0; + this->window_buffer = nullptr; + this->pixel_buffer = nullptr; + this->active = false; + this->setup = false; - if (_cocoa_subdriver != nullptr && _cocoa_subdriver->IsFullscreen()) { - /* Full screen, there is only one possible resolution. */ - NSSize screen = [ [ _cocoa_subdriver->window screen ] frame ].size; - _resolutions.emplace_back((uint)screen.width, (uint)screen.height); - } else { - /* Windowed; offer a selection of common window sizes up until the - * maximum usable screen space. This excludes the menu and dock areas. */ - NSSize maxSize = [ [ NSScreen mainScreen] visibleFrame ].size; - for (const auto &d : _default_resolutions) { - if (d.width < maxSize.width && d.height < maxSize.height) _resolutions.push_back(d); - } - _resolutions.emplace_back((uint)maxSize.width, (uint)maxSize.height); - } -} + this->window = nil; + this->cocoaview = nil; + this->color_space = nullptr; + this->cgcontext = nullptr; -static FVideoDriver_Cocoa iFVideoDriver_Cocoa; + this->num_dirty_rects = MAX_DIRTY_RECTS; +} /** * Stop the cocoa video subdriver. @@ -108,8 +104,14 @@ void VideoDriver_Cocoa::Stop() CocoaExitApplication(); - delete _cocoa_subdriver; - _cocoa_subdriver = nullptr; + /* Release window mode resources */ + if (this->window != nil) [ this->window close ]; + + CGContextRelease(this->cgcontext); + + CGColorSpaceRelease(this->color_space); + free(this->window_buffer); + free(this->pixel_buffer); _cocoa_video_started = false; } @@ -139,18 +141,17 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm) return "The cocoa quartz subdriver only supports 8 and 32 bpp."; } - _cocoa_subdriver = new WindowQuartzSubdriver(); - if (!_cocoa_subdriver->ChangeResolution(width, height, bpp)) { + if (!this->ChangeResolution(width, height, bpp)) { Stop(); return "Could not create subdriver"; } - if (_fullscreen) _cocoa_subdriver->ToggleFullscreen(_fullscreen); + if (_fullscreen) this->ToggleFullscreen(_fullscreen); this->GameSizeChanged(); this->UpdateVideoModes(); - return NULL; + return nullptr; } /** @@ -163,9 +164,13 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm) */ void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height) { - assert(_cocoa_subdriver != NULL); - - _cocoa_subdriver->MakeDirty(left, top, width, height); + if (this->num_dirty_rects < MAX_DIRTY_RECTS) { + dirty_rects[this->num_dirty_rects].left = left; + dirty_rects[this->num_dirty_rects].top = top; + dirty_rects[this->num_dirty_rects].right = left + width; + dirty_rects[this->num_dirty_rects].bottom = top + height; + } + this->num_dirty_rects++; } /** @@ -190,12 +195,8 @@ void VideoDriver_Cocoa::MainLoop() */ bool VideoDriver_Cocoa::ChangeResolution(int w, int h) { - assert(_cocoa_subdriver != NULL); - - bool ret = _cocoa_subdriver->ChangeResolution(w, h, BlitterFactory::GetCurrentBlitter()->GetScreenDepth()); - + bool ret = this->ChangeResolution(w, h, BlitterFactory::GetCurrentBlitter()->GetScreenDepth()); this->GameSizeChanged(); - return ret; } @@ -207,11 +208,15 @@ bool VideoDriver_Cocoa::ChangeResolution(int w, int h) */ bool VideoDriver_Cocoa::ToggleFullscreen(bool full_screen) { - assert(_cocoa_subdriver != NULL); + if (this->IsFullscreen() == full_screen) return true; - bool res = _cocoa_subdriver->ToggleFullscreen(full_screen); - this->UpdateVideoModes(); - return res; + if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) { + [ this->window performSelector:@selector(toggleFullScreen:) withObject:this->window ]; + this->UpdateVideoModes(); + return true; + } + + return false; } /** @@ -229,7 +234,7 @@ bool VideoDriver_Cocoa::AfterBlitterChange() */ void VideoDriver_Cocoa::EditBoxLostFocus() { - if (_cocoa_subdriver != NULL) [ [ _cocoa_subdriver->cocoaview inputContext ] discardMarkedText ]; + [ [ this->cocoaview inputContext ] discardMarkedText ]; /* Clear any marked string from the current edit box. */ HandleTextInput(NULL, true); } @@ -248,16 +253,14 @@ Dimension VideoDriver_Cocoa::GetScreenSize() const */ void VideoDriver_Cocoa::GameSizeChanged() { - if (_cocoa_subdriver == nullptr) return; - /* Tell the game that the resolution has changed */ - _screen.width = _cocoa_subdriver->GetWidth(); - _screen.height = _cocoa_subdriver->GetHeight(); - _screen.pitch = _cocoa_subdriver->GetWidth(); - _screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer(); + _screen.width = this->window_width; + _screen.height = this->window_height; + _screen.pitch = this->window_width; + _screen.dst_ptr = this->GetPixelBuffer(); /* Store old window size if we entered fullscreen mode. */ - bool fullscreen = _cocoa_subdriver->IsFullscreen(); + bool fullscreen = this->IsFullscreen(); if (fullscreen && !_fullscreen) this->orig_res = _cur_resolution; _fullscreen = fullscreen; @@ -266,18 +269,16 @@ void VideoDriver_Cocoa::GameSizeChanged() ::GameSizeChanged(); } -class WindowQuartzSubdriver; - /* Subclass of OTTD_CocoaView to fix Quartz rendering */ @interface OTTD_QuartzView : OTTD_CocoaView -- (void)setDriver:(WindowQuartzSubdriver *)drv; +- (void)setDriver:(VideoDriver_Cocoa *)drv; - (void)drawRect:(NSRect)invalidRect; @end @implementation OTTD_QuartzView -- (void)setDriver:(WindowQuartzSubdriver *)drv +- (void)setDriver:(VideoDriver_Cocoa *)drv { driver = drv; } @@ -360,7 +361,29 @@ class WindowQuartzSubdriver; @end -void WindowQuartzSubdriver::GetDeviceInfo() +/** + * Update the video modus. + */ +void VideoDriver_Cocoa::UpdateVideoModes() +{ + _resolutions.clear(); + + if (this->IsFullscreen()) { + /* Full screen, there is only one possible resolution. */ + NSSize screen = [ [ this->window screen ] frame ].size; + _resolutions.emplace_back((uint)screen.width, (uint)screen.height); + } else { + /* Windowed; offer a selection of common window sizes up until the + * maximum usable screen space. This excludes the menu and dock areas. */ + NSSize maxSize = [ [ NSScreen mainScreen] visibleFrame ].size; + for (const auto &d : _default_resolutions) { + if (d.width < maxSize.width && d.height < maxSize.height) _resolutions.push_back(d); + } + _resolutions.emplace_back((uint)maxSize.width, (uint)maxSize.height); + } +} + +void VideoDriver_Cocoa::GetDeviceInfo() { /* Initialize the video settings; this data persists between mode switches * and gather some information that is useful to know about the display */ @@ -375,27 +398,12 @@ void WindowQuartzSubdriver::GetDeviceInfo() CGDisplayModeRelease(cur_mode); } -bool WindowQuartzSubdriver::IsFullscreen() +bool VideoDriver_Cocoa::IsFullscreen() { return this->window != nil && ([ this->window styleMask ] & NSWindowStyleMaskFullScreen) != 0; } -/** Switch to full screen mode on OSX 10.7 - * @return Whether we switched to full screen - */ -bool WindowQuartzSubdriver::ToggleFullscreen(bool fullscreen) -{ - if (this->IsFullscreen() == fullscreen) return true; - - if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) { - [ this->window performSelector:@selector(toggleFullScreen:) withObject:this->window ]; - return true; - } - - return false; -} - -bool WindowQuartzSubdriver::SetVideoMode(int width, int height, int bpp) +bool VideoDriver_Cocoa::SetVideoMode(int width, int height, int bpp) { this->setup = true; this->GetDeviceInfo(); @@ -508,7 +516,7 @@ bool WindowQuartzSubdriver::SetVideoMode(int width, int height, int bpp) return ret; } -void WindowQuartzSubdriver::BlitIndexedToView32(int left, int top, int right, int bottom) +void VideoDriver_Cocoa::BlitIndexedToView32(int left, int top, int right, int bottom) { const uint32 *pal = this->palette; const uint8 *src = (uint8*)this->pixel_buffer; @@ -524,37 +532,7 @@ void WindowQuartzSubdriver::BlitIndexedToView32(int left, int top, int right, in } -WindowQuartzSubdriver::WindowQuartzSubdriver() -{ - this->window_width = 0; - this->window_height = 0; - this->buffer_depth = 0; - this->window_buffer = NULL; - this->pixel_buffer = NULL; - this->active = false; - this->setup = false; - - this->window = nil; - this->cocoaview = nil; - - this->cgcontext = NULL; - - this->num_dirty_rects = MAX_DIRTY_RECTS; -} - -WindowQuartzSubdriver::~WindowQuartzSubdriver() -{ - /* Release window mode resources */ - if (this->window != nil) [ this->window close ]; - - CGContextRelease(this->cgcontext); - - CGColorSpaceRelease(this->color_space); - free(this->window_buffer); - free(this->pixel_buffer); -} - -void WindowQuartzSubdriver::Draw(bool force_update) +void VideoDriver_Cocoa::Draw(bool force_update) { PerformanceMeasurer framerate(PFE_VIDEO); @@ -596,18 +574,7 @@ void WindowQuartzSubdriver::Draw(bool force_update) this->num_dirty_rects = 0; } -void WindowQuartzSubdriver::MakeDirty(int left, int top, int width, int height) -{ - if (this->num_dirty_rects < MAX_DIRTY_RECTS) { - dirty_rects[this->num_dirty_rects].left = left; - dirty_rects[this->num_dirty_rects].top = top; - dirty_rects[this->num_dirty_rects].right = left + width; - dirty_rects[this->num_dirty_rects].bottom = top + height; - } - this->num_dirty_rects++; -} - -void WindowQuartzSubdriver::UpdatePalette(uint first_color, uint num_colors) +void VideoDriver_Cocoa::UpdatePalette(uint first_color, uint num_colors) { if (this->buffer_depth != 8) return; @@ -622,7 +589,7 @@ void WindowQuartzSubdriver::UpdatePalette(uint first_color, uint num_colors) this->num_dirty_rects = MAX_DIRTY_RECTS; } -bool WindowQuartzSubdriver::ChangeResolution(int w, int h, int bpp) +bool VideoDriver_Cocoa::ChangeResolution(int w, int h, int bpp) { int old_width = this->window_width; int old_height = this->window_height; @@ -635,7 +602,7 @@ bool WindowQuartzSubdriver::ChangeResolution(int w, int h, int bpp) } /* Convert local coordinate to window server (CoreGraphics) coordinate */ -CGPoint WindowQuartzSubdriver::PrivateLocalToCG(NSPoint *p) +CGPoint VideoDriver_Cocoa::PrivateLocalToCG(NSPoint *p) { p->y = this->window_height - p->y; @@ -651,7 +618,7 @@ CGPoint WindowQuartzSubdriver::PrivateLocalToCG(NSPoint *p) return cgp; } -NSPoint WindowQuartzSubdriver::GetMouseLocation(NSEvent *event) +NSPoint VideoDriver_Cocoa::GetMouseLocation(NSEvent *event) { NSPoint pt; @@ -666,7 +633,7 @@ NSPoint WindowQuartzSubdriver::GetMouseLocation(NSEvent *event) return pt; } -bool WindowQuartzSubdriver::MouseIsInsideView(NSPoint *pt) +bool VideoDriver_Cocoa::MouseIsInsideView(NSPoint *pt) { return [ cocoaview mouse:*pt inRect:[ this->cocoaview bounds ] ]; } @@ -682,7 +649,7 @@ static void ClearWindowBuffer(uint32 *buffer, uint32 pitch, uint32 height) } } -bool WindowQuartzSubdriver::WindowResized() +bool VideoDriver_Cocoa::WindowResized() { if (this->window == nil || this->cocoaview == nil) return true; @@ -722,7 +689,7 @@ bool WindowQuartzSubdriver::WindowResized() } } - static_cast(VideoDriver::GetInstance())->GameSizeChanged(); + this->GameSizeChanged(); /* Redraw screen */ this->num_dirty_rects = MAX_DIRTY_RECTS; @@ -730,4 +697,28 @@ bool WindowQuartzSubdriver::WindowResized() return true; } +void VideoDriver_Cocoa::CheckPaletteAnim() +{ + if (_cur_palette.count_dirty != 0) { + Blitter *blitter = BlitterFactory::GetCurrentBlitter(); + + switch (blitter->UsePaletteAnimation()) { + case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND: + this->UpdatePalette(_cur_palette.first_dirty, _cur_palette.count_dirty); + break; + + case Blitter::PALETTE_ANIMATION_BLITTER: + blitter->PaletteAnimate(_cur_palette); + break; + + case Blitter::PALETTE_ANIMATION_NONE: + break; + + default: + NOT_REACHED(); + } + _cur_palette.count_dirty = 0; + } +} + #endif /* WITH_COCOA */ diff --git a/src/video/cocoa/cocoa_wnd.h b/src/video/cocoa/cocoa_wnd.h index 837532e9e0..35dcaa7c07 100644 --- a/src/video/cocoa/cocoa_wnd.h +++ b/src/video/cocoa/cocoa_wnd.h @@ -12,7 +12,7 @@ #import -class WindowQuartzSubdriver; +class VideoDriver_Cocoa; extern NSString *OTTDMainLaunchGameEngine; @@ -23,10 +23,10 @@ extern NSString *OTTDMainLaunchGameEngine; /** Subclass of NSWindow to cater our special needs */ @interface OTTD_CocoaWindow : NSWindow { - WindowQuartzSubdriver *driver; + VideoDriver_Cocoa *driver; } -- (void)setDriver:(WindowQuartzSubdriver *)drv; +- (void)setDriver:(VideoDriver_Cocoa *)drv; - (void)miniaturize:(id)sender; - (void)display; @@ -39,10 +39,10 @@ extern NSString *OTTDMainLaunchGameEngine; /** Subclass of NSView to fix Quartz rendering and mouse awareness */ @interface OTTD_CocoaView : NSView { - WindowQuartzSubdriver *driver; + VideoDriver_Cocoa *driver; NSTrackingRectTag trackingtag; } -- (void)setDriver:(WindowQuartzSubdriver *)drv; +- (void)setDriver:(VideoDriver_Cocoa *)drv; - (void)drawRect:(NSRect)rect; - (BOOL)isOpaque; - (BOOL)acceptsFirstResponder; @@ -59,10 +59,10 @@ extern NSString *OTTDMainLaunchGameEngine; /** Delegate for our NSWindow to send ask for quit on close */ @interface OTTD_CocoaWindowDelegate : NSObject { - WindowQuartzSubdriver *driver; + VideoDriver_Cocoa *driver; } -- (void)setDriver:(WindowQuartzSubdriver *)drv; +- (void)setDriver:(VideoDriver_Cocoa *)drv; - (BOOL)windowShouldClose:(id)sender; - (void)windowDidEnterFullScreen:(NSNotification *)aNotification; diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index 256565b2b8..ff222ea425 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -70,7 +70,7 @@ static OTTDMain *_ottd_main; - (void)launchGameEngine: (NSNotification*) note { /* Setup cursor for the current _game_mode. */ - [ _cocoa_subdriver->cocoaview resetCursorRects ]; + [ static_cast(VideoDriver::GetInstance())->cocoaview resetCursorRects ]; /* Hand off to main application code. */ static_cast(VideoDriver::GetInstance())->GameLoop(); @@ -282,7 +282,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel @implementation OTTD_CocoaWindow -- (void)setDriver:(WindowQuartzSubdriver *)drv +- (void)setDriver:(VideoDriver_Cocoa *)drv { driver = drv; } @@ -404,7 +404,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) /** * Initialize the driver */ -- (void)setDriver:(WindowQuartzSubdriver *)drv +- (void)setDriver:(VideoDriver_Cocoa *)drv { driver = drv; } @@ -491,7 +491,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) */ - (void)mouseExited:(NSEvent *)theEvent { - if (_cocoa_subdriver != NULL) UndrawMouseCursor(); + if (driver->window != nil) UndrawMouseCursor(); _cursor.in_window = false; } @@ -810,7 +810,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) @implementation OTTD_CocoaWindowDelegate /** Initialize the video driver */ -- (void)setDriver:(WindowQuartzSubdriver *)drv +- (void)setDriver:(VideoDriver_Cocoa *)drv { driver = drv; } diff --git a/src/video/cocoa/event.mm b/src/video/cocoa/event.mm index 8a43945f5d..86ffd9145a 100644 --- a/src/video/cocoa/event.mm +++ b/src/video/cocoa/event.mm @@ -73,15 +73,13 @@ static uint32 GetTick() return tim.tv_usec / 1000 + tim.tv_sec * 1000; } -static void QZ_WarpCursor(int x, int y) +void VideoDriver_Cocoa::WarpCursor(int x, int y) { - assert(_cocoa_subdriver != NULL); - /* Only allow warping when in foreground */ if (![ NSApp isActive ]) return; NSPoint p = NSMakePoint(x, y); - CGPoint cgp = _cocoa_subdriver->PrivateLocalToCG(&p); + CGPoint cgp = this->PrivateLocalToCG(&p); /* Do the actual warp */ CGWarpMouseCursorPosition(cgp); @@ -90,32 +88,6 @@ static void QZ_WarpCursor(int x, int y) } -static void QZ_CheckPaletteAnim() -{ - if (_cur_palette.count_dirty != 0) { - Blitter *blitter = BlitterFactory::GetCurrentBlitter(); - - switch (blitter->UsePaletteAnimation()) { - case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND: - _cocoa_subdriver->UpdatePalette(_cur_palette.first_dirty, _cur_palette.count_dirty); - break; - - case Blitter::PALETTE_ANIMATION_BLITTER: - blitter->PaletteAnimate(_cur_palette); - break; - - case Blitter::PALETTE_ANIMATION_NONE: - break; - - default: - NOT_REACHED(); - } - _cur_palette.count_dirty = 0; - } -} - - - struct VkMapping { unsigned short vk_from; byte map_to; @@ -343,10 +315,10 @@ static void QZ_DoUnsidedModifiers(unsigned int newMods) _current_mods = newMods; } -static void QZ_MouseMovedEvent(int x, int y) +void VideoDriver_Cocoa::MouseMovedEvent(int x, int y) { if (_cursor.UpdateCursorPosition(x, y, false)) { - QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y); + this->WarpCursor(_cursor.pos.x, _cursor.pos.y); } HandleMouseEvents(); } @@ -380,10 +352,8 @@ static void QZ_MouseButtonEvent(int button, BOOL down) -static bool QZ_PollEvent() +bool VideoDriver_Cocoa::PollEvent() { - assert(_cocoa_subdriver != NULL); - #ifdef _DEBUG uint32 et0 = GetTick(); #endif @@ -395,7 +365,7 @@ static bool QZ_PollEvent() #endif if (event == nil) return false; - if (!_cocoa_subdriver->IsActive()) { + if (!this->active) { [ NSApp sendEvent:event ]; return true; } @@ -408,18 +378,18 @@ static bool QZ_PollEvent() case NSMouseMoved: case NSOtherMouseDragged: case NSLeftMouseDragged: - pt = _cocoa_subdriver->GetMouseLocation(event); - if (!_cocoa_subdriver->MouseIsInsideView(&pt) && !_emulating_right_button) { + pt = this->GetMouseLocation(event); + if (!this->MouseIsInsideView(&pt) && !_emulating_right_button) { [ NSApp sendEvent:event ]; break; } - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + this->MouseMovedEvent((int)pt.x, (int)pt.y); break; case NSRightMouseDragged: - pt = _cocoa_subdriver->GetMouseLocation(event); - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + pt = this->GetMouseLocation(event); + this->MouseMovedEvent((int)pt.x, (int)pt.y); break; case NSLeftMouseDown: @@ -428,13 +398,13 @@ static bool QZ_PollEvent() if (_settings_client.gui.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSCommandKeyMask; if (_settings_client.gui.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSControlKeyMask; - pt = _cocoa_subdriver->GetMouseLocation(event); + pt = this->GetMouseLocation(event); - if (!([ event modifierFlags ] & keymask) || !_cocoa_subdriver->MouseIsInsideView(&pt)) { + if (!([ event modifierFlags ] & keymask) || !this->MouseIsInsideView(&pt)) { [ NSApp sendEvent:event ]; } - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + this->MouseMovedEvent((int)pt.x, (int)pt.y); /* Right mouse button emulation */ if ([ event modifierFlags ] & keymask) { @@ -448,9 +418,9 @@ static bool QZ_PollEvent() case NSLeftMouseUp: [ NSApp sendEvent:event ]; - pt = _cocoa_subdriver->GetMouseLocation(event); + pt = this->GetMouseLocation(event); - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + this->MouseMovedEvent((int)pt.x, (int)pt.y); /* Right mouse button emulation */ if (_emulating_right_button) { @@ -462,24 +432,24 @@ static bool QZ_PollEvent() break; case NSRightMouseDown: - pt = _cocoa_subdriver->GetMouseLocation(event); - if (!_cocoa_subdriver->MouseIsInsideView(&pt)) { + pt = this->GetMouseLocation(event); + if (!this->MouseIsInsideView(&pt)) { [ NSApp sendEvent:event ]; break; } - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + this->MouseMovedEvent((int)pt.x, (int)pt.y); QZ_MouseButtonEvent(1, YES); break; case NSRightMouseUp: - pt = _cocoa_subdriver->GetMouseLocation(event); - if (!_cocoa_subdriver->MouseIsInsideView(&pt)) { + pt = this->GetMouseLocation(event); + if (!this->MouseIsInsideView(&pt)) { [ NSApp sendEvent:event ]; break; } - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + this->MouseMovedEvent((int)pt.x, (int)pt.y); QZ_MouseButtonEvent(1, NO); break; @@ -492,7 +462,7 @@ static bool QZ_PollEvent() break; } - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + this->MouseMovedEvent((int)pt.x, (int)pt.y); QZ_MouseButtonEvent([ event buttonNumber ], YES); break; @@ -503,7 +473,7 @@ static bool QZ_PollEvent() break; } - QZ_MouseMovedEvent((int)pt.x, (int)pt.y); + this->MouseMovedEvent((int)pt.x, (int)pt.y); QZ_MouseButtonEvent([ event buttonNumber ], NO); break; #endif @@ -524,7 +494,7 @@ static bool QZ_PollEvent() unsigned short unicode = [ chars length ] > 0 ? [ chars characterAtIndex:0 ] : 0; if (EditBoxInGlobalFocus()) { if (QZ_KeyEvent([ event keyCode ], unicode, YES)) { - [ _cocoa_subdriver->cocoaview interpretKeyEvents:[ NSArray arrayWithObject:event ] ]; + [ this->cocoaview interpretKeyEvents:[ NSArray arrayWithObject:event ] ]; } } else { QZ_KeyEvent([ event keyCode ], unicode, YES); @@ -629,32 +599,32 @@ void VideoDriver_Cocoa::GameLoop() #endif DisplaySplashImage(); - QZ_CheckPaletteAnim(); - _cocoa_subdriver->Draw(true); + this->CheckPaletteAnim(); + this->Draw(true); CSleep(1); for (int i = 0; i < 2; i++) ::GameLoop(); UpdateWindows(); - QZ_CheckPaletteAnim(); - _cocoa_subdriver->Draw(); + this->CheckPaletteAnim(); + this->Draw(); CSleep(1); /* Set the proper OpenTTD palette which got spoilt by the splash * image when using 8bpp blitter */ GfxInitPalettes(); - QZ_CheckPaletteAnim(); - _cocoa_subdriver->Draw(true); + this->CheckPaletteAnim(); + this->Draw(true); for (;;) { uint32 prev_cur_ticks = cur_ticks; // to check for wrapping InteractiveRandom(); // randomness - while (QZ_PollEvent()) {} + while (this->PollEvent()) {} if (_exit_game) { /* Restore saved resolution if in fullscreen mode. */ - if (_cocoa_subdriver->IsFullscreen()) _cur_resolution = this->orig_res; + if (this->IsFullscreen()) _cur_resolution = this->orig_res; break; } @@ -685,8 +655,8 @@ void VideoDriver_Cocoa::GameLoop() ::GameLoop(); UpdateWindows(); - QZ_CheckPaletteAnim(); - _cocoa_subdriver->Draw(); + this->CheckPaletteAnim(); + this->Draw(); } else { #ifdef _DEBUG uint32 st0 = GetTick(); @@ -697,7 +667,7 @@ void VideoDriver_Cocoa::GameLoop() #endif NetworkDrawChatMessage(); DrawMouseCursor(); - _cocoa_subdriver->Draw(); + this->Draw(); } } From 13134f9d64fb873ab1ee0cca2cc4c4fa3c64a44a Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Thu, 11 Feb 2021 22:54:43 +0100 Subject: [PATCH 046/122] Codechange: [OSX] Replace #define with modern code. --- src/video/cocoa/cocoa_v.h | 5 +++-- src/video/cocoa/cocoa_v.mm | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index adeadd3bef..70adb18b5c 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -32,9 +32,10 @@ private: void *pixel_buffer; ///< used for direct pixel access void *window_buffer; ///< Colour translation from palette to screen -# define MAX_DIRTY_RECTS 100 + static const int MAX_DIRTY_RECTS = 100; + Rect dirty_rects[MAX_DIRTY_RECTS]; ///< dirty rectangles - int num_dirty_rects = MAX_DIRTY_RECTS; ///< Number of dirty rectangles + uint num_dirty_rects; ///< Number of dirty rectangles uint32 palette[256]; ///< Colour Palette public: diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 9d1fe37820..9e52cce01f 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -92,7 +92,7 @@ VideoDriver_Cocoa::VideoDriver_Cocoa() this->color_space = nullptr; this->cgcontext = nullptr; - this->num_dirty_rects = MAX_DIRTY_RECTS; + this->num_dirty_rects = lengthof(this->dirty_rects); } /** @@ -164,7 +164,7 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm) */ void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height) { - if (this->num_dirty_rects < MAX_DIRTY_RECTS) { + if (this->num_dirty_rects < lengthof(this->dirty_rects)) { dirty_rects[this->num_dirty_rects].left = left; dirty_rects[this->num_dirty_rects].top = top; dirty_rects[this->num_dirty_rects].right = left + width; @@ -539,7 +539,7 @@ void VideoDriver_Cocoa::Draw(bool force_update) /* Check if we need to do anything */ if (this->num_dirty_rects == 0 || [ this->window isMiniaturized ]) return; - if (this->num_dirty_rects >= MAX_DIRTY_RECTS) { + if (this->num_dirty_rects >= lengthof(this->dirty_rects)) { this->num_dirty_rects = 1; this->dirty_rects[0].left = 0; this->dirty_rects[0].top = 0; @@ -548,7 +548,7 @@ void VideoDriver_Cocoa::Draw(bool force_update) } /* Build the region of dirty rectangles */ - for (int i = 0; i < this->num_dirty_rects; i++) { + for (uint i = 0; i < this->num_dirty_rects; i++) { /* We only need to blit in indexed mode since in 32bpp mode the game draws directly to the image. */ if (this->buffer_depth == 8) { BlitIndexedToView32( @@ -586,7 +586,7 @@ void VideoDriver_Cocoa::UpdatePalette(uint first_color, uint num_colors) this->palette[i] = clr; } - this->num_dirty_rects = MAX_DIRTY_RECTS; + this->num_dirty_rects = lengthof(this->dirty_rects); } bool VideoDriver_Cocoa::ChangeResolution(int w, int h, int bpp) @@ -692,7 +692,7 @@ bool VideoDriver_Cocoa::WindowResized() this->GameSizeChanged(); /* Redraw screen */ - this->num_dirty_rects = MAX_DIRTY_RECTS; + this->num_dirty_rects = lengthof(this->dirty_rects); return true; } From 8ced72ab10b12a33ef5e71f8b346f63a0db0f862 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sun, 24 Jan 2021 13:12:55 +0100 Subject: [PATCH 047/122] Codechange: [OSX] Inline some functions that are used in only one place. --- src/video/cocoa/cocoa_v.h | 15 --------------- src/video/cocoa/cocoa_v.mm | 32 ++++++++++++++------------------ src/video/cocoa/event.mm | 26 +++++++++----------------- 3 files changed, 23 insertions(+), 50 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index 70adb18b5c..bae0f52898 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -22,7 +22,6 @@ private: int device_width; ///< Width of device in pixel int device_height; ///< Height of device in pixel - int device_depth; ///< Colour depth of device in bit int window_width; ///< Current window width in pixel int window_height; ///< Current window height in pixel @@ -142,25 +141,11 @@ private: /** Update the palette */ void UpdatePalette(uint first_color, uint num_colors); - uint ListModes(OTTD_Point *modes, uint max_modes); - - /** Change window resolution - * @param w New window width - * @param h New window height - * @return Whether change was successful - */ - bool ChangeResolution(int w, int h, int bpp); - /** Are we in fullscreen mode * @return whether fullscreen mode is currently used */ bool IsFullscreen(); - /** Return the current pixel buffer - * @return pixelbuffer - */ - void *GetPixelBuffer() { return buffer_depth == 8 ? pixel_buffer : window_buffer; } - /** Return the mouse location * @param event UI event * @return mouse location as NSPoint diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 9e52cce01f..3350e32b8c 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -141,7 +141,7 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm) return "The cocoa quartz subdriver only supports 8 and 32 bpp."; } - if (!this->ChangeResolution(width, height, bpp)) { + if (!this->SetVideoMode(width, height, bpp)) { Stop(); return "Could not create subdriver"; } @@ -195,9 +195,17 @@ void VideoDriver_Cocoa::MainLoop() */ bool VideoDriver_Cocoa::ChangeResolution(int w, int h) { - bool ret = this->ChangeResolution(w, h, BlitterFactory::GetCurrentBlitter()->GetScreenDepth()); - this->GameSizeChanged(); - return ret; + int old_width = this->window_width; + int old_height = this->window_height; + int old_bpp = this->buffer_depth; + + if (this->SetVideoMode(w, h, BlitterFactory::GetCurrentBlitter()->GetScreenDepth())) { + this->GameSizeChanged(); + return true; + } + + if (old_width != 0 && old_height != 0) this->SetVideoMode(old_width, old_height, old_bpp); + return false; } /** @@ -257,7 +265,7 @@ void VideoDriver_Cocoa::GameSizeChanged() _screen.width = this->window_width; _screen.height = this->window_height; _screen.pitch = this->window_width; - _screen.dst_ptr = this->GetPixelBuffer(); + _screen.dst_ptr = this->buffer_depth == 8 ? this->pixel_buffer : this->window_buffer; /* Store old window size if we entered fullscreen mode. */ bool fullscreen = this->IsFullscreen(); @@ -508,7 +516,7 @@ bool VideoDriver_Cocoa::SetVideoMode(int width, int height, int bpp) if (this->color_space == nullptr) this->color_space = CGColorSpaceCreateDeviceRGB(); if (this->color_space == nullptr) error("Could not get a valid colour space for drawing."); - bool ret = WindowResized(); + bool ret = this->WindowResized(); this->UpdatePalette(0, 256); this->setup = false; @@ -589,18 +597,6 @@ void VideoDriver_Cocoa::UpdatePalette(uint first_color, uint num_colors) this->num_dirty_rects = lengthof(this->dirty_rects); } -bool VideoDriver_Cocoa::ChangeResolution(int w, int h, int bpp) -{ - int old_width = this->window_width; - int old_height = this->window_height; - int old_bpp = this->buffer_depth; - - if (this->SetVideoMode(w, h, bpp)) return true; - if (old_width != 0 && old_height != 0) this->SetVideoMode(old_width, old_height, old_bpp); - - return false; -} - /* Convert local coordinate to window server (CoreGraphics) coordinate */ CGPoint VideoDriver_Cocoa::PrivateLocalToCG(NSPoint *p) { diff --git a/src/video/cocoa/event.mm b/src/video/cocoa/event.mm index 86ffd9145a..858cd51ad5 100644 --- a/src/video/cocoa/event.mm +++ b/src/video/cocoa/event.mm @@ -73,21 +73,6 @@ static uint32 GetTick() return tim.tv_usec / 1000 + tim.tv_sec * 1000; } -void VideoDriver_Cocoa::WarpCursor(int x, int y) -{ - /* Only allow warping when in foreground */ - if (![ NSApp isActive ]) return; - - NSPoint p = NSMakePoint(x, y); - CGPoint cgp = this->PrivateLocalToCG(&p); - - /* Do the actual warp */ - CGWarpMouseCursorPosition(cgp); - /* this is the magic call that fixes cursor "freezing" after warp */ - CGAssociateMouseAndMouseCursorPosition(true); -} - - struct VkMapping { unsigned short vk_from; byte map_to; @@ -317,8 +302,15 @@ static void QZ_DoUnsidedModifiers(unsigned int newMods) void VideoDriver_Cocoa::MouseMovedEvent(int x, int y) { - if (_cursor.UpdateCursorPosition(x, y, false)) { - this->WarpCursor(_cursor.pos.x, _cursor.pos.y); + if (_cursor.UpdateCursorPosition(x, y, false) && [ NSApp isActive ]) { + /* Warping cursor when in foreground */ + NSPoint p = NSMakePoint(_cursor.pos.x, _cursor.pos.y); + CGPoint cgp = this->PrivateLocalToCG(&p); + + /* Do the actual warp */ + CGWarpMouseCursorPosition(cgp); + /* this is the magic call that fixes cursor "freezing" after warp */ + CGAssociateMouseAndMouseCursorPosition(true); } HandleMouseEvents(); } From 0eff7de6594b0b0cb451a992287239b04eb66fc4 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sun, 24 Jan 2021 13:13:25 +0100 Subject: [PATCH 048/122] Cleanup: [OSX] Doxygen comment style in video driver. --- src/video/cocoa/cocoa_v.h | 88 +++++--------------------------------- src/video/cocoa/cocoa_v.mm | 37 +++++++++++++++- 2 files changed, 46 insertions(+), 79 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index bae0f52898..f9920c20e4 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -50,47 +50,18 @@ public: VideoDriver_Cocoa(); const char *Start(const StringList ¶m) override; - - /** Stop the video driver */ void Stop() override; + void MainLoop() override; - /** Mark dirty a screen region - * @param left x-coordinate of left border - * @param top y-coordinate of top border - * @param width width or dirty rectangle - * @param height height of dirty rectangle - */ void MakeDirty(int left, int top, int width, int height) override; + bool AfterBlitterChange() override; - /** Programme main loop */ - void MainLoop() override; - - /** Change window resolution - * @param w New window width - * @param h New window height - * @return Whether change was successful - */ bool ChangeResolution(int w, int h) override; - - /** Set a new window mode - * @param fullscreen Whether to set fullscreen mode or not - * @return Whether changing the screen mode was successful - */ bool ToggleFullscreen(bool fullscreen) override; - /** Callback invoked after the blitter was changed. - * @return True if no error. - */ - bool AfterBlitterChange() override; - - /** - * An edit box lost the input focus. Abort character compositing if necessary. - */ void EditBoxLostFocus() override; - /** Return driver name - * @return driver name - */ + /** Return driver name */ const char *GetName() const override { return "cocoa"; } /* --- The following methods should be private, but can't be due to Obj-C limitations. --- */ @@ -98,67 +69,30 @@ public: /** Main game loop. */ void GameLoop(); // In event.mm. - /** Resize the window. - * @return whether the window was successfully resized - */ bool WindowResized(); - /** Convert local coordinate to window server (CoreGraphics) coordinate - * @param p local coordinates - * @return window driver coordinates - */ - CGPoint PrivateLocalToCG(NSPoint *p); - protected: Dimension GetScreenSize() const override; private: + NSPoint GetMouseLocation(NSEvent *event); + bool MouseIsInsideView(NSPoint *pt); + CGPoint PrivateLocalToCG(NSPoint *p); bool PollEvent(); // In event.mm. void MouseMovedEvent(int x, int y); // In event.mm. - void WarpCursor(int x, int y); // In event.mm. + bool IsFullscreen(); void GameSizeChanged(); - void UpdateVideoModes(); - - /** - * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode. - * - * @param left The x coord for the left edge of the box to blit. - * @param top The y coord for the top edge of the box to blit. - * @param right The x coord for the right edge of the box to blit. - * @param bottom The y coord for the bottom edge of the box to blit. - */ - void BlitIndexedToView32(int left, int top, int right, int bottom); + void UpdateVideoModes(); void GetDeviceInfo(); bool SetVideoMode(int width, int height, int bpp); - /** Draw window - * @param force_update Whether to redraw unconditionally - */ - void Draw(bool force_update = false); - - /** Update the palette */ void UpdatePalette(uint first_color, uint num_colors); - - /** Are we in fullscreen mode - * @return whether fullscreen mode is currently used - */ - bool IsFullscreen(); - - /** Return the mouse location - * @param event UI event - * @return mouse location as NSPoint - */ - NSPoint GetMouseLocation(NSEvent *event); - - /** Return whether the mouse is within our view - * @param pt Mouse coordinates - * @return Whether mouse coordinates are within view - */ - bool MouseIsInsideView(NSPoint *pt); - void CheckPaletteAnim(); + + void Draw(bool force_update = false); + void BlitIndexedToView32(int left, int top, int right, int bottom); }; class FVideoDriver_Cocoa : public DriverFactoryBase { diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 3350e32b8c..2525af1d45 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -406,6 +406,10 @@ void VideoDriver_Cocoa::GetDeviceInfo() CGDisplayModeRelease(cur_mode); } +/** + * Are we in fullscreen mode + * @return whether fullscreen mode is currently used + */ bool VideoDriver_Cocoa::IsFullscreen() { return this->window != nil && ([ this->window styleMask ] & NSWindowStyleMaskFullScreen) != 0; @@ -524,6 +528,14 @@ bool VideoDriver_Cocoa::SetVideoMode(int width, int height, int bpp) return ret; } +/** + * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode. + * + * @param left The x coord for the left edge of the box to blit. + * @param top The y coord for the top edge of the box to blit. + * @param right The x coord for the right edge of the box to blit. + * @param bottom The y coord for the bottom edge of the box to blit. + */ void VideoDriver_Cocoa::BlitIndexedToView32(int left, int top, int right, int bottom) { const uint32 *pal = this->palette; @@ -539,7 +551,9 @@ void VideoDriver_Cocoa::BlitIndexedToView32(int left, int top, int right, int bo } } - +/** Draw window + * @param force_update Whether to redraw unconditionally + */ void VideoDriver_Cocoa::Draw(bool force_update) { PerformanceMeasurer framerate(PFE_VIDEO); @@ -582,6 +596,7 @@ void VideoDriver_Cocoa::Draw(bool force_update) this->num_dirty_rects = 0; } +/** Update the palette */ void VideoDriver_Cocoa::UpdatePalette(uint first_color, uint num_colors) { if (this->buffer_depth != 8) return; @@ -597,7 +612,11 @@ void VideoDriver_Cocoa::UpdatePalette(uint first_color, uint num_colors) this->num_dirty_rects = lengthof(this->dirty_rects); } -/* Convert local coordinate to window server (CoreGraphics) coordinate */ +/** + * Convert local coordinate to window server (CoreGraphics) coordinate + * @param p local coordinates + * @return window driver coordinates + */ CGPoint VideoDriver_Cocoa::PrivateLocalToCG(NSPoint *p) { @@ -614,6 +633,11 @@ CGPoint VideoDriver_Cocoa::PrivateLocalToCG(NSPoint *p) return cgp; } +/** + * Return the mouse location + * @param event UI event + * @return mouse location as NSPoint + */ NSPoint VideoDriver_Cocoa::GetMouseLocation(NSEvent *event) { NSPoint pt; @@ -629,6 +653,11 @@ NSPoint VideoDriver_Cocoa::GetMouseLocation(NSEvent *event) return pt; } +/** + * Return whether the mouse is within our view + * @param pt Mouse coordinates + * @return Whether mouse coordinates are within view + */ bool VideoDriver_Cocoa::MouseIsInsideView(NSPoint *pt) { return [ cocoaview mouse:*pt inRect:[ this->cocoaview bounds ] ]; @@ -645,6 +674,10 @@ static void ClearWindowBuffer(uint32 *buffer, uint32 pitch, uint32 height) } } +/** + * Resize the window. + * @return whether the window was successfully resized + */ bool VideoDriver_Cocoa::WindowResized() { if (this->window == nil || this->cocoaview == nil) return true; From 8aaf4ea0987a361e5d4f7b690544ee9119746660 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 30 Jan 2021 19:13:29 +0100 Subject: [PATCH 049/122] Codechange: [OSX] Split Window and back buffer creation in Cocoa video driver. --- src/video/cocoa/cocoa_v.h | 13 +- src/video/cocoa/cocoa_v.mm | 255 +++++++++++++++-------------------- src/video/cocoa/cocoa_wnd.h | 7 +- src/video/cocoa/cocoa_wnd.mm | 31 +++-- 4 files changed, 133 insertions(+), 173 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index f9920c20e4..0961e1660d 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -16,13 +16,12 @@ extern bool _cocoa_video_started; +@class OTTD_CocoaWindowDelegate; + class VideoDriver_Cocoa : public VideoDriver { private: Dimension orig_res; ///< Saved window size for non-fullscreen mode. - int device_width; ///< Width of device in pixel - int device_height; ///< Height of device in pixel - int window_width; ///< Current window width in pixel int window_height; ///< Current window height in pixel int window_pitch; @@ -46,6 +45,8 @@ public: CGColorSpaceRef color_space; ///< Window color space CGContextRef cgcontext; ///< Context reference for Quartz subdriver + OTTD_CocoaWindowDelegate *delegate; //!< Window delegate object + public: VideoDriver_Cocoa(); @@ -69,7 +70,7 @@ public: /** Main game loop. */ void GameLoop(); // In event.mm. - bool WindowResized(); + void AllocateBackingStore(); protected: Dimension GetScreenSize() const override; @@ -85,8 +86,8 @@ private: void GameSizeChanged(); void UpdateVideoModes(); - void GetDeviceInfo(); - bool SetVideoMode(int width, int height, int bpp); + + bool MakeWindow(int width, int height); void UpdatePalette(uint first_color, uint num_colors); void CheckPaletteAnim(); diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 2525af1d45..7f1e10bfe4 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -76,6 +76,12 @@ static const Dimension _default_resolutions[] = { static FVideoDriver_Cocoa iFVideoDriver_Cocoa; +/* Subclass of OTTD_CocoaView to fix Quartz rendering */ +@interface OTTD_QuartzView : OTTD_CocoaView +- (void)drawRect:(NSRect)invalidRect; +@end + + VideoDriver_Cocoa::VideoDriver_Cocoa() { this->window_width = 0; @@ -88,6 +94,7 @@ VideoDriver_Cocoa::VideoDriver_Cocoa() this->window = nil; this->cocoaview = nil; + this->delegate = nil; this->color_space = nullptr; this->cgcontext = nullptr; @@ -106,10 +113,12 @@ void VideoDriver_Cocoa::Stop() /* Release window mode resources */ if (this->window != nil) [ this->window close ]; + [ this->cocoaview release ]; + [ this->delegate release ]; CGContextRelease(this->cgcontext); - CGColorSpaceRelease(this->color_space); + free(this->window_buffer); free(this->pixel_buffer); @@ -130,23 +139,21 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm) if (!CocoaSetupApplication()) return nullptr; this->UpdateAutoResolution(); - this->orig_res = _cur_resolution; - int width = _cur_resolution.width; - int height = _cur_resolution.height; - int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth(); + int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth(); if (bpp != 8 && bpp != 32) { Stop(); return "The cocoa quartz subdriver only supports 8 and 32 bpp."; } - if (!this->SetVideoMode(width, height, bpp)) { + bool fullscreen = _fullscreen; + if (!this->MakeWindow(_cur_resolution.width, _cur_resolution.height)) { Stop(); - return "Could not create subdriver"; + return "Could not create window"; } - if (_fullscreen) this->ToggleFullscreen(_fullscreen); + if (fullscreen) this->ToggleFullscreen(fullscreen); this->GameSizeChanged(); this->UpdateVideoModes(); @@ -195,17 +202,29 @@ void VideoDriver_Cocoa::MainLoop() */ bool VideoDriver_Cocoa::ChangeResolution(int w, int h) { - int old_width = this->window_width; - int old_height = this->window_height; - int old_bpp = this->buffer_depth; + NSSize screen_size = [ [ NSScreen mainScreen ] frame ].size; + w = std::min(w, (int)screen_size.width); + h = std::min(h, (int)screen_size.height); - if (this->SetVideoMode(w, h, BlitterFactory::GetCurrentBlitter()->GetScreenDepth())) { - this->GameSizeChanged(); - return true; + NSRect contentRect = NSMakeRect(0, 0, w, h); + [ this->window setContentSize:contentRect.size ]; + + /* Ensure frame height - title bar height >= view height */ + float content_height = [ this->window contentRectForFrameRect:[ this->window frame ] ].size.height; + contentRect.size.height = Clamp(h, 0, (int)content_height); + + if (this->cocoaview != nil) { + h = (int)contentRect.size.height; + [ this->cocoaview setFrameSize:contentRect.size ]; } - if (old_width != 0 && old_height != 0) this->SetVideoMode(old_width, old_height, old_bpp); - return false; + this->window_width = w; + this->window_height = h; + + [ (OTTD_CocoaWindow *)this->window center ]; + this->AllocateBackingStore(); + + return true; } /** @@ -234,7 +253,9 @@ bool VideoDriver_Cocoa::ToggleFullscreen(bool full_screen) */ bool VideoDriver_Cocoa::AfterBlitterChange() { - return this->ChangeResolution(_screen.width, _screen.height); + this->ChangeResolution(_screen.width, _screen.height); + this->UpdatePalette(0, 256); + return true; } /** @@ -256,6 +277,15 @@ Dimension VideoDriver_Cocoa::GetScreenSize() const return { static_cast(NSWidth(frame)), static_cast(NSHeight(frame)) }; } +/** + * Are we in fullscreen mode + * @return whether fullscreen mode is currently used + */ +bool VideoDriver_Cocoa::IsFullscreen() +{ + return this->window != nil && ([ this->window styleMask ] & NSWindowStyleMaskFullScreen) != 0; +} + /** * Handle a change of the display area. */ @@ -277,19 +307,10 @@ void VideoDriver_Cocoa::GameSizeChanged() ::GameSizeChanged(); } -/* Subclass of OTTD_CocoaView to fix Quartz rendering */ -@interface OTTD_QuartzView : OTTD_CocoaView -- (void)setDriver:(VideoDriver_Cocoa *)drv; -- (void)drawRect:(NSRect)invalidRect; -@end @implementation OTTD_QuartzView -- (void)setDriver:(VideoDriver_Cocoa *)drv -{ - driver = drv; -} - (void)drawRect:(NSRect)invalidRect { if (driver->cgcontext == NULL) return; @@ -391,141 +412,85 @@ void VideoDriver_Cocoa::UpdateVideoModes() } } -void VideoDriver_Cocoa::GetDeviceInfo() -{ - /* Initialize the video settings; this data persists between mode switches - * and gather some information that is useful to know about the display */ - - /* Use the new API when compiling for OSX 10.6 or later */ - CGDisplayModeRef cur_mode = CGDisplayCopyDisplayMode(kCGDirectMainDisplay); - if (cur_mode == NULL) { return; } - - this->device_width = CGDisplayModeGetWidth(cur_mode); - this->device_height = CGDisplayModeGetHeight(cur_mode); - - CGDisplayModeRelease(cur_mode); -} - /** - * Are we in fullscreen mode - * @return whether fullscreen mode is currently used + * Build window and view with a given size. + * @param width Window width. + * @param height Window height. */ -bool VideoDriver_Cocoa::IsFullscreen() -{ - return this->window != nil && ([ this->window styleMask ] & NSWindowStyleMaskFullScreen) != 0; -} - -bool VideoDriver_Cocoa::SetVideoMode(int width, int height, int bpp) +bool VideoDriver_Cocoa::MakeWindow(int width, int height) { this->setup = true; - this->GetDeviceInfo(); - if (width > this->device_width) width = this->device_width; - if (height > this->device_height) height = this->device_height; + NSSize screen_size = [ [ NSScreen mainScreen ] frame ].size; + if (width > screen_size.width) width = screen_size.width; + if (height > screen_size.height) height = screen_size.height; NSRect contentRect = NSMakeRect(0, 0, width, height); - /* Check if we should recreate the window */ + /* Create main window. */ + unsigned int style = NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSClosableWindowMask; + this->window = [ [ OTTD_CocoaWindow alloc ] initWithContentRect:contentRect styleMask:style backing:NSBackingStoreBuffered defer:NO ]; if (this->window == nil) { - OTTD_CocoaWindowDelegate *delegate; - - /* Set the window style */ - unsigned int style = NSTitledWindowMask; - style |= (NSMiniaturizableWindowMask | NSClosableWindowMask); - style |= NSResizableWindowMask; - - /* Manually create a window, avoids having a nib file resource */ - this->window = [ [ OTTD_CocoaWindow alloc ] - initWithContentRect:contentRect - styleMask:style - backing:NSBackingStoreBuffered - defer:NO ]; - - if (this->window == nil) { - DEBUG(driver, 0, "Could not create the Cocoa window."); - this->setup = false; - return false; - } - - /* Add built in full-screen support when available (OS X 10.7 and higher) - * This code actually compiles for 10.5 and later, but only makes sense in conjunction - * with the quartz fullscreen support as found only in 10.7 and later - */ - if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) { - NSWindowCollectionBehavior behavior = [ this->window collectionBehavior ]; - behavior |= NSWindowCollectionBehaviorFullScreenPrimary; - [ this->window setCollectionBehavior:behavior ]; - - NSButton* fullscreenButton = [ this->window standardWindowButton:NSWindowFullScreenButton ]; - [ fullscreenButton setAction:@selector(toggleFullScreen:) ]; - [ fullscreenButton setTarget:this->window ]; - } - - [ this->window setDriver:this ]; - - char caption[50]; - snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); - NSString *nsscaption = [ [ NSString alloc ] initWithUTF8String:caption ]; - [ this->window setTitle:nsscaption ]; - [ this->window setMiniwindowTitle:nsscaption ]; - [ nsscaption release ]; - - [ this->window setContentMinSize:NSMakeSize(64.0f, 64.0f) ]; + DEBUG(driver, 0, "Could not create the Cocoa window."); + this->setup = false; + return false; + } + [ this->window setDriver:this ]; - [ this->window setAcceptsMouseMovedEvents:YES ]; - [ this->window setViewsNeedDisplay:NO ]; + /* Add built in full-screen support when available (OS X 10.7 and higher) + * This code actually compiles for 10.5 and later, but only makes sense in conjunction + * with the quartz fullscreen support as found only in 10.7 and later + */ + if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) { + NSWindowCollectionBehavior behavior = [ this->window collectionBehavior ]; + behavior |= NSWindowCollectionBehaviorFullScreenPrimary; + [ this->window setCollectionBehavior:behavior ]; - delegate = [ [ OTTD_CocoaWindowDelegate alloc ] init ]; - [ delegate setDriver:this ]; - [ this->window setDelegate:[ delegate autorelease ] ]; - } else { - /* We already have a window, just change its size */ - [ this->window setContentSize:contentRect.size ]; + NSButton* fullscreenButton = [ this->window standardWindowButton:NSWindowFullScreenButton ]; + [ fullscreenButton setAction:@selector(toggleFullScreen:) ]; + [ fullscreenButton setTarget:this->window ]; + } - /* Ensure frame height - title bar height >= view height */ - float content_height = [ this->window contentRectForFrameRect:[ this->window frame ] ].size.height; - contentRect.size.height = Clamp(height, 0, (int)content_height); + char caption[50]; + snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); + NSString *nsscaption = [ [ NSString alloc ] initWithUTF8String:caption ]; + [ this->window setTitle:nsscaption ]; + [ this->window setMiniwindowTitle:nsscaption ]; + [ nsscaption release ]; - if (this->cocoaview != nil) { - height = (int)contentRect.size.height; - [ this->cocoaview setFrameSize:contentRect.size ]; - } - } + [ this->window setContentMinSize:NSMakeSize(64.0f, 64.0f) ]; - this->window_width = width; - this->window_height = height; - this->buffer_depth = bpp; + this->delegate = [ [ OTTD_CocoaWindowDelegate alloc ] initWithDriver:this ]; + [ this->window setDelegate:this->delegate ]; + [ this->window setAcceptsMouseMovedEvents:YES ]; + [ this->window setViewsNeedDisplay:NO ]; [ (OTTD_CocoaWindow *)this->window center ]; + [ this->window makeKeyAndOrderFront:nil ]; - /* Only recreate the view if it doesn't already exist */ + /* Create content view. */ + this->cocoaview = [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->window contentRectForFrameRect:[ this->window frame ] ] andDriver:this ]; if (this->cocoaview == nil) { - this->cocoaview = [ [ OTTD_QuartzView alloc ] initWithFrame:contentRect ]; - if (this->cocoaview == nil) { - DEBUG(driver, 0, "Could not create the Quartz view."); - this->setup = false; - return false; - } - - [ this->cocoaview setDriver:this ]; - - [ (NSView*)this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; - [ this->window setContentView:cocoaview ]; - [ this->cocoaview release ]; - [ this->window makeKeyAndOrderFront:nil ]; + DEBUG(driver, 0, "Could not create the Quartz view."); + this->setup = false; + return false; } + [ (NSView*)this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; + [ this->window setContentView:cocoaview ]; + [ this->window setColorSpace:[ NSColorSpace sRGBColorSpace ] ]; + CGColorSpaceRelease(this->color_space); this->color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); if (this->color_space == nullptr) this->color_space = CGColorSpaceCreateDeviceRGB(); if (this->color_space == nullptr) error("Could not get a valid colour space for drawing."); - bool ret = this->WindowResized(); - this->UpdatePalette(0, 256); - this->setup = false; - return ret; + this->UpdatePalette(0, 256); + this->AllocateBackingStore(); + + return true; } /** @@ -624,7 +589,7 @@ CGPoint VideoDriver_Cocoa::PrivateLocalToCG(NSPoint *p) *p = [ this->cocoaview convertPoint:*p toView:nil ]; *p = [ this->window convertRectToScreen:NSMakeRect(p->x, p->y, 0, 0) ].origin; - p->y = this->device_height - p->y; + p->y = NSScreen.screens[0].frame.size.height - p->y; CGPoint cgp; cgp.x = p->x; @@ -674,18 +639,16 @@ static void ClearWindowBuffer(uint32 *buffer, uint32 pitch, uint32 height) } } -/** - * Resize the window. - * @return whether the window was successfully resized - */ -bool VideoDriver_Cocoa::WindowResized() +/** Resize the window. */ +void VideoDriver_Cocoa::AllocateBackingStore() { - if (this->window == nil || this->cocoaview == nil) return true; + if (this->window == nil || this->cocoaview == nil || this->setup) return; NSRect newframe = [ this->cocoaview frame ]; this->window_width = (int)newframe.size.width; this->window_height = (int)newframe.size.height; + this->buffer_depth = BlitterFactory::GetCurrentBlitter()->GetScreenDepth(); /* Create Core Graphics Context */ free(this->window_buffer); @@ -712,18 +675,12 @@ bool VideoDriver_Cocoa::WindowResized() if (this->buffer_depth == 8) { free(this->pixel_buffer); this->pixel_buffer = malloc(this->window_width * this->window_height); - if (this->pixel_buffer == NULL) { - DEBUG(driver, 0, "Failed to allocate pixel buffer"); - return false; - } + if (this->pixel_buffer == nullptr) usererror("Out of memory allocating pixel buffer"); } - this->GameSizeChanged(); - /* Redraw screen */ this->num_dirty_rects = lengthof(this->dirty_rects); - - return true; + this->GameSizeChanged(); } void VideoDriver_Cocoa::CheckPaletteAnim() diff --git a/src/video/cocoa/cocoa_wnd.h b/src/video/cocoa/cocoa_wnd.h index 35dcaa7c07..96834d9047 100644 --- a/src/video/cocoa/cocoa_wnd.h +++ b/src/video/cocoa/cocoa_wnd.h @@ -42,7 +42,7 @@ extern NSString *OTTDMainLaunchGameEngine; VideoDriver_Cocoa *driver; NSTrackingRectTag trackingtag; } -- (void)setDriver:(VideoDriver_Cocoa *)drv; +- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv; - (void)drawRect:(NSRect)rect; - (BOOL)isOpaque; - (BOOL)acceptsFirstResponder; @@ -61,12 +61,11 @@ extern NSString *OTTDMainLaunchGameEngine; { VideoDriver_Cocoa *driver; } - -- (void)setDriver:(VideoDriver_Cocoa *)drv; +- (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv; - (BOOL)windowShouldClose:(id)sender; - (void)windowDidEnterFullScreen:(NSNotification *)aNotification; -- (void)windowDidChangeScreenProfile:(NSNotification *)aNotification; +- (void)windowDidChangeBackingProperties:(NSNotification *)notification; - (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions; @end diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index ff222ea425..80c2e2e215 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -324,10 +324,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel { [ super setFrame:frameRect display:flag ]; - /* Don't do anything if the window is currently being created */ - if (driver->setup) return; - - if (!driver->WindowResized()) error("Cocoa: Failed to resize window."); + driver->AllocateBackingStore(); } /** * Handle hiding of the application @@ -401,12 +398,13 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) } @implementation OTTD_CocoaView -/** - * Initialize the driver - */ -- (void)setDriver:(VideoDriver_Cocoa *)drv + +- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv { - driver = drv; + if (self = [ super initWithFrame:frameRect ]) { + self->driver = drv; + } + return self; } /** * Define the opaqueness of the window / screen @@ -810,9 +808,12 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) @implementation OTTD_CocoaWindowDelegate /** Initialize the video driver */ -- (void)setDriver:(VideoDriver_Cocoa *)drv +- (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv { - driver = drv; + if (self = [ super init ]) { + self->driver = drv; + } + return self; } /** Handle closure requests */ - (BOOL)windowShouldClose:(id)sender @@ -854,10 +855,11 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) [ e release ]; } } -/** The colour profile of the screen the window is on changed. */ -- (void)windowDidChangeScreenProfile:(NSNotification *)aNotification +/** Screen the window is on changed. */ +- (void)windowDidChangeBackingProperties:(NSNotification *)notification { - if (!driver->setup) driver->WindowResized(); + /* Reallocate screen buffer if necessary. */ + driver->AllocateBackingStore(); } /** Presentation options to use for fullsreen mode. */ @@ -867,4 +869,5 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) } @end + #endif /* WITH_COCOA */ From 60f30036f1338b6fba8a0c23d7a8b70eab697644 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 6 Feb 2021 01:51:38 +0100 Subject: [PATCH 050/122] Codechange: [OSX] Drain autoreleased objects in each game loop cycle. --- src/video/cocoa/cocoa_wnd.mm | 19 ++++++---- src/video/cocoa/event.mm | 73 +++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index 80c2e2e215..e7c916443b 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -203,6 +203,7 @@ bool CocoaSetupApplication() } /* Become the front process, important when start from the command line. */ + [ [ NSApplication sharedApplication ] setActivationPolicy:NSApplicationActivationPolicyRegular ]; [ [ NSApplication sharedApplication ] activateIgnoringOtherApps:YES ]; /* Set up the menubar */ @@ -247,15 +248,17 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel return; } - NSAlert *alert = [ [ NSAlert alloc ] init ]; - [ alert setAlertStyle: NSCriticalAlertStyle ]; - [ alert setMessageText:[ NSString stringWithUTF8String:title ] ]; - [ alert setInformativeText:[ NSString stringWithUTF8String:message ] ]; - [ alert addButtonWithTitle: [ NSString stringWithUTF8String:buttonLabel ] ]; - [ alert runModal ]; - [ alert release ]; + @autoreleasepool { + NSAlert *alert = [ [ NSAlert alloc ] init ]; + [ alert setAlertStyle: NSCriticalAlertStyle ]; + [ alert setMessageText:[ NSString stringWithUTF8String:title ] ]; + [ alert setInformativeText:[ NSString stringWithUTF8String:message ] ]; + [ alert addButtonWithTitle: [ NSString stringWithUTF8String:buttonLabel ] ]; + [ alert runModal ]; + [ alert release ]; + } - if (!wasstarted && VideoDriver::GetInstance() != NULL) VideoDriver::GetInstance()->Stop(); + if (!wasstarted && VideoDriver::GetInstance() != nullptr) VideoDriver::GetInstance()->Stop(); _cocoa_video_dialog = false; } diff --git a/src/video/cocoa/event.mm b/src/video/cocoa/event.mm index 858cd51ad5..00559e6bce 100644 --- a/src/video/cocoa/event.mm +++ b/src/video/cocoa/event.mm @@ -609,57 +609,60 @@ void VideoDriver_Cocoa::GameLoop() this->Draw(true); for (;;) { - uint32 prev_cur_ticks = cur_ticks; // to check for wrapping - InteractiveRandom(); // randomness + @autoreleasepool { - while (this->PollEvent()) {} + uint32 prev_cur_ticks = cur_ticks; // to check for wrapping + InteractiveRandom(); // randomness - if (_exit_game) { - /* Restore saved resolution if in fullscreen mode. */ - if (this->IsFullscreen()) _cur_resolution = this->orig_res; - break; - } + while (this->PollEvent()) {} + + if (_exit_game) { + /* Restore saved resolution if in fullscreen mode. */ + if (this->IsFullscreen()) _cur_resolution = this->orig_res; + break; + } #if defined(_DEBUG) - if (_current_mods & NSShiftKeyMask) + if (_current_mods & NSShiftKeyMask) #else - if (_tab_is_down) + if (_tab_is_down) #endif - { - if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; - } else if (_fast_forward & 2) { - _fast_forward = 0; - } + { + if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; + } else if (_fast_forward & 2) { + _fast_forward = 0; + } - cur_ticks = GetTick(); - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) { - _realtime_tick += cur_ticks - last_cur_ticks; - last_cur_ticks = cur_ticks; - next_tick = cur_ticks + MILLISECONDS_PER_TICK; + cur_ticks = GetTick(); + if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) { + _realtime_tick += cur_ticks - last_cur_ticks; + last_cur_ticks = cur_ticks; + next_tick = cur_ticks + MILLISECONDS_PER_TICK; - bool old_ctrl_pressed = _ctrl_pressed; + bool old_ctrl_pressed = _ctrl_pressed; - _ctrl_pressed = !!(_current_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)); - _shift_pressed = !!(_current_mods & NSShiftKeyMask); + _ctrl_pressed = !!(_current_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)); + _shift_pressed = !!(_current_mods & NSShiftKeyMask); - if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); + if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); - ::GameLoop(); + ::GameLoop(); - UpdateWindows(); - this->CheckPaletteAnim(); - this->Draw(); - } else { + UpdateWindows(); + this->CheckPaletteAnim(); + this->Draw(); + } else { #ifdef _DEBUG - uint32 st0 = GetTick(); + uint32 st0 = GetTick(); #endif - CSleep(1); + CSleep(1); #ifdef _DEBUG - st += GetTick() - st0; + st += GetTick() - st0; #endif - NetworkDrawChatMessage(); - DrawMouseCursor(); - this->Draw(); + NetworkDrawChatMessage(); + DrawMouseCursor(); + this->Draw(); + } } } From 42af13c141eb74f63e58827f8a33e7f66d7d829a Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sun, 31 Jan 2021 18:58:23 +0100 Subject: [PATCH 051/122] Codechange: [OSX] Split drawing into its own subview. This allows the drawing backend code to be independent of any event or command handling. --- src/video/cocoa/cocoa_v.mm | 53 ++++++++++-- src/video/cocoa/cocoa_wnd.h | 19 +---- src/video/cocoa/cocoa_wnd.mm | 157 +++++++++++++++++------------------ 3 files changed, 125 insertions(+), 104 deletions(-) diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 7f1e10bfe4..ef30519ea6 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -77,7 +77,11 @@ static FVideoDriver_Cocoa iFVideoDriver_Cocoa; /* Subclass of OTTD_CocoaView to fix Quartz rendering */ -@interface OTTD_QuartzView : OTTD_CocoaView +@interface OTTD_QuartzView : NSView { + VideoDriver_Cocoa *driver; +} +- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv; + - (void)drawRect:(NSRect)invalidRect; @end @@ -311,6 +315,28 @@ void VideoDriver_Cocoa::GameSizeChanged() @implementation OTTD_QuartzView +- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv +{ + if (self = [ super initWithFrame:frameRect ]) { + self->driver = drv; + } + return self; +} + +- (BOOL)acceptsFirstResponder +{ + return NO; +} + +/** + * Define the opaqueness of the window / screen + * @return opaqueness of window / screen + */ +- (BOOL)isOpaque +{ + return YES; +} + - (void)drawRect:(NSRect)invalidRect { if (driver->cgcontext == NULL) return; @@ -429,13 +455,12 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) /* Create main window. */ unsigned int style = NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSClosableWindowMask; - this->window = [ [ OTTD_CocoaWindow alloc ] initWithContentRect:contentRect styleMask:style backing:NSBackingStoreBuffered defer:NO ]; + this->window = [ [ OTTD_CocoaWindow alloc ] initWithContentRect:contentRect styleMask:style backing:NSBackingStoreBuffered defer:NO driver:this ]; if (this->window == nil) { DEBUG(driver, 0, "Could not create the Cocoa window."); this->setup = false; return false; } - [ this->window setDriver:this ]; /* Add built in full-screen support when available (OS X 10.7 and higher) * This code actually compiles for 10.5 and later, but only makes sense in conjunction @@ -468,16 +493,28 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) [ (OTTD_CocoaWindow *)this->window center ]; [ this->window makeKeyAndOrderFront:nil ]; - /* Create content view. */ - this->cocoaview = [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->window contentRectForFrameRect:[ this->window frame ] ] andDriver:this ]; + /* Create wrapper view for text input. */ + NSRect view_frame = [ this->window contentRectForFrameRect:[ this->window frame ] ]; + this->cocoaview = [ [ OTTD_CocoaView alloc ] initWithFrame:view_frame andDriver:this ]; if (this->cocoaview == nil) { - DEBUG(driver, 0, "Could not create the Quartz view."); + DEBUG(driver, 0, "Could not create the text wrapper view."); + this->setup = false; + return false; + } + [ (NSView *)this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; + + /* Create content view. */ + NSView *draw_view = [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->cocoaview bounds ] andDriver:this ]; + if (draw_view == nil) { + DEBUG(driver, 0, "Could not create the drawing view."); this->setup = false; return false; } + [ draw_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; - [ (NSView*)this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; - [ this->window setContentView:cocoaview ]; + [ this->window setContentView:this->cocoaview ]; + [ this->cocoaview addSubview:draw_view ]; + [ draw_view release ]; [ this->window setColorSpace:[ NSColorSpace sRGBColorSpace ] ]; CGColorSpaceRelease(this->color_space); diff --git a/src/video/cocoa/cocoa_wnd.h b/src/video/cocoa/cocoa_wnd.h index 96834d9047..744a657861 100644 --- a/src/video/cocoa/cocoa_wnd.h +++ b/src/video/cocoa/cocoa_wnd.h @@ -22,31 +22,23 @@ extern NSString *OTTDMainLaunchGameEngine; @end /** Subclass of NSWindow to cater our special needs */ -@interface OTTD_CocoaWindow : NSWindow { - VideoDriver_Cocoa *driver; -} - -- (void)setDriver:(VideoDriver_Cocoa *)drv; +@interface OTTD_CocoaWindow : NSWindow +- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv; - (void)miniaturize:(id)sender; - (void)display; - (void)setFrame:(NSRect)frameRect display:(BOOL)flag; - (void)appDidHide:(NSNotification*)note; - (void)appDidUnhide:(NSNotification*)note; -- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag; @end /** Subclass of NSView to fix Quartz rendering and mouse awareness */ -@interface OTTD_CocoaView : NSView -{ +@interface OTTD_CocoaView : NSView { VideoDriver_Cocoa *driver; - NSTrackingRectTag trackingtag; } - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv; -- (void)drawRect:(NSRect)rect; -- (BOOL)isOpaque; + - (BOOL)acceptsFirstResponder; -- (BOOL)becomeFirstResponder; - (void)setTrackingRect; - (void)clearTrackingRect; - (void)resetCursorRects; @@ -58,9 +50,6 @@ extern NSString *OTTDMainLaunchGameEngine; /** Delegate for our NSWindow to send ask for quit on close */ @interface OTTD_CocoaWindowDelegate : NSObject -{ - VideoDriver_Cocoa *driver; -} - (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv; - (BOOL)windowShouldClose:(id)sender; diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index e7c916443b..ac6d35dd21 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -48,6 +48,46 @@ NSString *OTTDMainLaunchGameEngine = @"ottdmain_launch_game_engine"; static bool _cocoa_video_dialog = false; static OTTDMain *_ottd_main; + +/** + * Count the number of UTF-16 code points in a range of an UTF-8 string. + * @param from Start of the range. + * @param to End of the range. + * @return Number of UTF-16 code points in the range. + */ +static NSUInteger CountUtf16Units(const char *from, const char *to) +{ + NSUInteger i = 0; + + while (from < to) { + WChar c; + size_t len = Utf8Decode(&c, from); + i += len < 4 ? 1 : 2; // Watch for surrogate pairs. + from += len; + } + + return i; +} + +/** + * Advance an UTF-8 string by a number of equivalent UTF-16 code points. + * @param str UTF-8 string. + * @param count Number of UTF-16 code points to advance the string by. + * @return Advanced string pointer. + */ +static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) +{ + for (NSUInteger i = 0; i < count && *str != '\0'; ) { + WChar c; + size_t len = Utf8Decode(&c, str); + i += len < 4 ? 1 : 2; // Watch for surrogates. + str += len; + } + + return str; +} + + /** * The main class of the application, the application's delegate. */ @@ -69,11 +109,13 @@ static OTTDMain *_ottd_main; */ - (void)launchGameEngine: (NSNotification*) note { + auto *drv = static_cast(VideoDriver::GetInstance()); + /* Setup cursor for the current _game_mode. */ - [ static_cast(VideoDriver::GetInstance())->cocoaview resetCursorRects ]; + [ drv->window invalidateCursorRectsForView:[ drv->window contentView ] ]; /* Hand off to main application code. */ - static_cast(VideoDriver::GetInstance())->GameLoop(); + drv->GameLoop(); /* We are done, thank you for playing. */ [ self performSelectorOnMainThread:@selector(stopEngine) withObject:nil waitUntilDone:FALSE ]; @@ -283,12 +325,10 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel } @end -@implementation OTTD_CocoaWindow - -- (void)setDriver:(VideoDriver_Cocoa *)drv -{ - driver = drv; +@implementation OTTD_CocoaWindow { + VideoDriver_Cocoa *driver; } + /** * Minimize the window */ @@ -346,62 +386,28 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel /** * Initialize event system for the application rectangle */ -- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag +- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv { - /* Make our window subclass receive these application notifications */ - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; - - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; + if (self = [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]) { + /* Make our window subclass receive these application notifications */ + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; - return [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]; -} + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; -@end - - - -/** - * Count the number of UTF-16 code points in a range of an UTF-8 string. - * @param from Start of the range. - * @param to End of the range. - * @return Number of UTF-16 code points in the range. - */ -static NSUInteger CountUtf16Units(const char *from, const char *to) -{ - NSUInteger i = 0; - - while (from < to) { - WChar c; - size_t len = Utf8Decode(&c, from); - i += len < 4 ? 1 : 2; // Watch for surrogate pairs. - from += len; + self->driver = drv; } - return i; + return self; } -/** - * Advance an UTF-8 string by a number of equivalent UTF-16 code points. - * @param str UTF-8 string. - * @param count Number of UTF-16 code points to advance the string by. - * @return Advanced string pointer. - */ -static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) -{ - for (NSUInteger i = 0; i < count && *str != '\0'; ) { - WChar c; - size_t len = Utf8Decode(&c, str); - i += len < 4 ? 1 : 2; // Watch for surrogates. - str += len; - } +@end - return str; +@implementation OTTD_CocoaView { + NSTrackingRectTag trackingtag; } -@implementation OTTD_CocoaView - - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv { if (self = [ super initWithFrame:frameRect ]) { @@ -409,22 +415,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) } return self; } -/** - * Define the opaqueness of the window / screen - * @return opaqueness of window / screen - */ -- (BOOL)isOpaque -{ - return YES; -} -/** - * Draws a rectangle on the screen. - * It's overwritten by the individual drivers but must be defined - */ -- (void)drawRect:(NSRect)invalidRect -{ - return; -} + /** * Allow to handle events */ @@ -432,13 +423,15 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) { return YES; } -/** - * Actually handle events - */ -- (BOOL)becomeFirstResponder + +- (void)setNeedsDisplayInRect:(NSRect)invalidRect { - return YES; + /* Drawing is handled by our sub-views. Just pass it along. */ + for ( NSView *v in [ self subviews ]) { + [ v setNeedsDisplayInRect:[ v convertRect:invalidRect fromView:self ] ]; + } } + /** * Define the rectangle where we draw our application window */ @@ -556,7 +549,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) /** Unmark the current marked text. */ - (void)unmarkText { - HandleTextInput(NULL, true); + HandleTextInput(nullptr, true); } /** Get the caret position. */ @@ -575,7 +568,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) size_t mark_len; const char *mark = _focused_window->GetMarkedText(&mark_len); - if (mark != NULL) { + if (mark != nullptr) { NSUInteger start = CountUtf16Units(_focused_window->GetFocusedText(), mark); NSUInteger len = CountUtf16Units(mark, mark + mark_len); @@ -591,7 +584,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) if (!EditBoxInGlobalFocus()) return NO; size_t len; - return _focused_window->GetMarkedText(&len) != NULL; + return _focused_window->GetMarkedText(&len) != nullptr; } /** Get a string corresponding to the given range. */ @@ -602,7 +595,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) NSString *s = [ NSString stringWithUTF8String:_focused_window->GetFocusedText() ]; NSRange valid_range = NSIntersectionRange(NSMakeRange(0, [ s length ]), theRange); - if (actualRange != NULL) *actualRange = valid_range; + if (actualRange != nullptr) *actualRange = valid_range; if (valid_range.length == 0) return nil; return [ [ [ NSAttributedString alloc ] initWithString:[ s substringWithRange:valid_range ] ] autorelease ]; @@ -611,7 +604,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) /** Get a string corresponding to the given range. */ - (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange { - return [ self attributedSubstringForProposedRange:theRange actualRange:NULL ]; + return [ self attributedSubstringForProposedRange:theRange actualRange:nil ]; } /** Get the current edit box string. */ @@ -632,7 +625,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) Point pt = { (int)view_pt.x, (int)[ self frame ].size.height - (int)view_pt.y }; const char *ch = _focused_window->GetTextCharacterAtPosition(pt); - if (ch == NULL) return NSNotFound; + if (ch == nullptr) return NSNotFound; return CountUtf16Units(_focused_window->GetFocusedText(), ch); } @@ -808,8 +801,10 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) @end +@implementation OTTD_CocoaWindowDelegate { + VideoDriver_Cocoa *driver; +} -@implementation OTTD_CocoaWindowDelegate /** Initialize the video driver */ - (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv { From 965ce1294726427bf31da33bb5c7650773f8aada Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Wed, 3 Feb 2021 22:43:06 +0100 Subject: [PATCH 052/122] Codechange: [OSX] Use newer mouse tracking API. --- src/video/cocoa/cocoa_v.mm | 2 +- src/video/cocoa/cocoa_wnd.h | 16 +----- src/video/cocoa/cocoa_wnd.mm | 101 +++++++++++++---------------------- 3 files changed, 40 insertions(+), 79 deletions(-) diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index ef30519ea6..185f28c84e 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -495,7 +495,7 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) /* Create wrapper view for text input. */ NSRect view_frame = [ this->window contentRectForFrameRect:[ this->window frame ] ]; - this->cocoaview = [ [ OTTD_CocoaView alloc ] initWithFrame:view_frame andDriver:this ]; + this->cocoaview = [ [ OTTD_CocoaView alloc ] initWithFrame:view_frame ]; if (this->cocoaview == nil) { DEBUG(driver, 0, "Could not create the text wrapper view."); this->setup = false; diff --git a/src/video/cocoa/cocoa_wnd.h b/src/video/cocoa/cocoa_wnd.h index 744a657861..449b457af1 100644 --- a/src/video/cocoa/cocoa_wnd.h +++ b/src/video/cocoa/cocoa_wnd.h @@ -32,20 +32,8 @@ extern NSString *OTTDMainLaunchGameEngine; - (void)appDidUnhide:(NSNotification*)note; @end -/** Subclass of NSView to fix Quartz rendering and mouse awareness */ -@interface OTTD_CocoaView : NSView { - VideoDriver_Cocoa *driver; -} -- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv; - -- (BOOL)acceptsFirstResponder; -- (void)setTrackingRect; -- (void)clearTrackingRect; -- (void)resetCursorRects; -- (void)viewWillMoveToWindow:(NSWindow *)win; -- (void)viewDidMoveToWindow; -- (void)mouseEntered:(NSEvent *)theEvent; -- (void)mouseExited:(NSEvent *)theEvent; +/** Subclass of NSView to support mouse awareness and text input. */ +@interface OTTD_CocoaView : NSView @end /** Delegate for our NSWindow to send ask for quit on close */ diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index ac6d35dd21..8f72836cca 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -112,7 +112,9 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) auto *drv = static_cast(VideoDriver::GetInstance()); /* Setup cursor for the current _game_mode. */ - [ drv->window invalidateCursorRectsForView:[ drv->window contentView ] ]; + NSEvent *e = [ [ NSEvent alloc ] init ]; + [ drv->cocoaview cursorUpdate:e ]; + [ e release ]; /* Hand off to main application code. */ drv->GameLoop(); @@ -329,6 +331,25 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel VideoDriver_Cocoa *driver; } +/** + * Initialize event system for the application rectangle + */ +- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv +{ + if (self = [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]) { + /* Make our window subclass receive these application notifications */ + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; + + [ [ NSNotificationCenter defaultCenter ] addObserver:self + selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; + + self->driver = drv; + } + + return self; +} + /** * Minimize the window */ @@ -383,38 +404,10 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel { driver->active = true; } -/** - * Initialize event system for the application rectangle - */ -- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv -{ - if (self = [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]) { - /* Make our window subclass receive these application notifications */ - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; - - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; - - self->driver = drv; - } - - return self; -} @end -@implementation OTTD_CocoaView { - NSTrackingRectTag trackingtag; -} - -- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv -{ - if (self = [ super initWithFrame:frameRect ]) { - self->driver = drv; - } - return self; -} +@implementation OTTD_CocoaView /** * Allow to handle events @@ -432,46 +425,26 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel } } -/** - * Define the rectangle where we draw our application window - */ -- (void)setTrackingRect +/** Update mouse cursor to use for this view. */ +- (void)cursorUpdate:(NSEvent *)event { - NSPoint loc = [ self convertPoint:[ [ self window ] mouseLocationOutsideOfEventStream ] fromView:nil ]; - BOOL inside = ([ self hitTest:loc ]==self); - if (inside) [ [ self window ] makeFirstResponder:self ]; - trackingtag = [ self addTrackingRect:[ self visibleRect ] owner:self userData:nil assumeInside:inside ]; + [ (_game_mode == GM_BOOTSTRAP ? [ NSCursor arrowCursor ] : [ NSCursor clearCocoaCursor ]) set ]; } -/** - * Return responsibility for the application window to system - */ -- (void)clearTrackingRect -{ - [ self removeTrackingRect:trackingtag ]; -} -/** - * Declare responsibility for the cursor within our application rect - */ -- (void)resetCursorRects -{ - [ super resetCursorRects ]; - [ self clearTrackingRect ]; - [ self setTrackingRect ]; - [ self addCursorRect:[ self bounds ] cursor:(_game_mode == GM_BOOTSTRAP ? [ NSCursor arrowCursor ] : [ NSCursor clearCocoaCursor ]) ]; -} -/** - * Prepare for moving the application window - */ + - (void)viewWillMoveToWindow:(NSWindow *)win { - if (!win && [ self window ]) [ self clearTrackingRect ]; + for (NSTrackingArea *a in [ self trackingAreas ]) { + [ self removeTrackingArea:a ]; + } } -/** - * Restore our responsibility for our application window after moving - */ + - (void)viewDidMoveToWindow { - if ([ self window ]) [ self setTrackingRect ]; + /* Install mouse tracking area. */ + NSTrackingAreaOptions track_opt = NSTrackingInVisibleRect | NSTrackingActiveInActiveApp | NSTrackingMouseEnteredAndExited | NSTrackingCursorUpdate; + NSTrackingArea *track = [ [ NSTrackingArea alloc ] initWithRect:[ self bounds ] options:track_opt owner:self userInfo:nil ]; + [ self addTrackingArea:track ]; + [ track release ]; } /** * Make OpenTTD aware that it has control over the mouse @@ -485,7 +458,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel */ - (void)mouseExited:(NSEvent *)theEvent { - if (driver->window != nil) UndrawMouseCursor(); + if ([ self window ] != nil) UndrawMouseCursor(); _cursor.in_window = false; } From bd42fc94cc1dc064d0c20cfa4fad108dc64931c6 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 6 Feb 2021 00:23:29 +0100 Subject: [PATCH 053/122] Codechange: [OSX] Move some things from video driver to our NSView. --- src/video/cocoa/cocoa_v.h | 6 +- src/video/cocoa/cocoa_v.mm | 221 ++++++++++++++++------------------- src/video/cocoa/cocoa_wnd.mm | 10 ++ 3 files changed, 116 insertions(+), 121 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index 0961e1660d..4c53cdb8fa 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -17,6 +17,8 @@ extern bool _cocoa_video_started; @class OTTD_CocoaWindowDelegate; +@class OTTD_CocoaWindow; +@class OTTD_CocoaView; class VideoDriver_Cocoa : public VideoDriver { private: @@ -40,8 +42,8 @@ public: bool active; ///< Whether the window is visible bool setup; - id window; ///< Pointer to window object - id cocoaview; ///< Pointer to view object + OTTD_CocoaWindow *window; ///< Pointer to window object + OTTD_CocoaView *cocoaview; ///< Pointer to view object CGColorSpaceRef color_space; ///< Window color space CGContextRef cgcontext; ///< Context reference for Quartz subdriver diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 185f28c84e..88f461234f 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -26,7 +26,6 @@ #include "../../openttd.h" #include "../../debug.h" -#include "../../rev.h" #include "../../core/geometry_type.hpp" #include "cocoa_v.h" #include "cocoa_wnd.h" @@ -311,111 +310,6 @@ void VideoDriver_Cocoa::GameSizeChanged() ::GameSizeChanged(); } - - -@implementation OTTD_QuartzView - -- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv -{ - if (self = [ super initWithFrame:frameRect ]) { - self->driver = drv; - } - return self; -} - -- (BOOL)acceptsFirstResponder -{ - return NO; -} - -/** - * Define the opaqueness of the window / screen - * @return opaqueness of window / screen - */ -- (BOOL)isOpaque -{ - return YES; -} - -- (void)drawRect:(NSRect)invalidRect -{ - if (driver->cgcontext == NULL) return; - - CGContextRef viewContext = (CGContextRef)[ [ NSGraphicsContext currentContext ] graphicsPort ]; - CGContextSetShouldAntialias(viewContext, FALSE); - CGContextSetInterpolationQuality(viewContext, kCGInterpolationNone); - - /* The obtained 'rect' is actually a union of all dirty rects, let's ask for an explicit list of rects instead */ - const NSRect *dirtyRects; - NSInteger dirtyRectCount; - [ self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount ]; - - /* We need an Image in order to do blitting, but as we don't touch the context between this call and drawing no copying will actually be done here */ - CGImageRef fullImage = CGBitmapContextCreateImage(driver->cgcontext); - - /* Calculate total area we are blitting */ - uint32 blitArea = 0; - for (int n = 0; n < dirtyRectCount; n++) { - blitArea += (uint32)(dirtyRects[n].size.width * dirtyRects[n].size.height); - } - - /* - * This might be completely stupid, but in my extremely subjective opinion it feels faster - * The point is, if we're blitting less than 50% of the dirty rect union then it's still a good idea to blit each dirty - * rect separately but if we blit more than that, it's just cheaper to blit the entire union in one pass. - * Feel free to remove or find an even better value than 50% ... / blackis - */ - NSRect frameRect = [ self frame ]; - if (blitArea / (float)(invalidRect.size.width * invalidRect.size.height) > 0.5f) { - NSRect rect = invalidRect; - CGRect clipRect; - CGRect blitRect; - - blitRect.origin.x = rect.origin.x; - blitRect.origin.y = rect.origin.y; - blitRect.size.width = rect.size.width; - blitRect.size.height = rect.size.height; - - clipRect.origin.x = rect.origin.x; - clipRect.origin.y = frameRect.size.height - rect.origin.y - rect.size.height; - - clipRect.size.width = rect.size.width; - clipRect.size.height = rect.size.height; - - /* Blit dirty part of image */ - CGImageRef clippedImage = CGImageCreateWithImageInRect(fullImage, clipRect); - CGContextDrawImage(viewContext, blitRect, clippedImage); - CGImageRelease(clippedImage); - } else { - for (int n = 0; n < dirtyRectCount; n++) { - NSRect rect = dirtyRects[n]; - CGRect clipRect; - CGRect blitRect; - - blitRect.origin.x = rect.origin.x; - blitRect.origin.y = rect.origin.y; - blitRect.size.width = rect.size.width; - blitRect.size.height = rect.size.height; - - clipRect.origin.x = rect.origin.x; - clipRect.origin.y = frameRect.size.height - rect.origin.y - rect.size.height; - - clipRect.size.width = rect.size.width; - clipRect.size.height = rect.size.height; - - /* Blit dirty part of image */ - CGImageRef clippedImage = CGImageCreateWithImageInRect(fullImage, clipRect); - CGContextDrawImage(viewContext, blitRect, clippedImage); - CGImageRelease(clippedImage); - } - } - - CGImageRelease(fullImage); -} - -@end - - /** * Update the video modus. */ @@ -476,21 +370,10 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) [ fullscreenButton setTarget:this->window ]; } - char caption[50]; - snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); - NSString *nsscaption = [ [ NSString alloc ] initWithUTF8String:caption ]; - [ this->window setTitle:nsscaption ]; - [ this->window setMiniwindowTitle:nsscaption ]; - [ nsscaption release ]; - - [ this->window setContentMinSize:NSMakeSize(64.0f, 64.0f) ]; - this->delegate = [ [ OTTD_CocoaWindowDelegate alloc ] initWithDriver:this ]; [ this->window setDelegate:this->delegate ]; - [ this->window setAcceptsMouseMovedEvents:YES ]; - [ this->window setViewsNeedDisplay:NO ]; - [ (OTTD_CocoaWindow *)this->window center ]; + [ this->window center ]; [ this->window makeKeyAndOrderFront:nil ]; /* Create wrapper view for text input. */ @@ -501,7 +384,7 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) this->setup = false; return false; } - [ (NSView *)this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; + [ this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; /* Create content view. */ NSView *draw_view = [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->cocoaview bounds ] andDriver:this ]; @@ -744,4 +627,104 @@ void VideoDriver_Cocoa::CheckPaletteAnim() } } + +@implementation OTTD_QuartzView + +- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv +{ + if (self = [ super initWithFrame:frameRect ]) { + self->driver = drv; + } + return self; +} + +- (BOOL)acceptsFirstResponder +{ + return NO; +} + +- (BOOL)isOpaque +{ + return YES; +} + +- (void)drawRect:(NSRect)invalidRect +{ + if (driver->cgcontext == nullptr) return; + + NSGraphicsContext *ctx = [ NSGraphicsContext currentContext ]; + CGContextRef viewContext = [ ctx respondsToSelector:@selector(CGContext) ] ? [ ctx CGContext ] : (CGContextRef)[ ctx graphicsPort ]; + CGContextSetShouldAntialias(viewContext, FALSE); + CGContextSetInterpolationQuality(viewContext, kCGInterpolationNone); + + /* The obtained 'rect' is actually a union of all dirty rects, let's ask for an explicit list of rects instead */ + const NSRect *dirtyRects; + NSInteger dirtyRectCount; + [ self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount ]; + + /* We need an Image in order to do blitting, but as we don't touch the context between this call and drawing no copying will actually be done here */ + CGImageRef fullImage = CGBitmapContextCreateImage(driver->cgcontext); + + /* Calculate total area we are blitting */ + uint32 blitArea = 0; + for (int n = 0; n < dirtyRectCount; n++) { + blitArea += (uint32)(dirtyRects[n].size.width * dirtyRects[n].size.height); + } + + /* + * This might be completely stupid, but in my extremely subjective opinion it feels faster + * The point is, if we're blitting less than 50% of the dirty rect union then it's still a good idea to blit each dirty + * rect separately but if we blit more than that, it's just cheaper to blit the entire union in one pass. + * Feel free to remove or find an even better value than 50% ... / blackis + */ + NSRect frameRect = [ self frame ]; + if (blitArea / (float)(invalidRect.size.width * invalidRect.size.height) > 0.5f) { + NSRect rect = invalidRect; + CGRect clipRect; + CGRect blitRect; + + blitRect.origin.x = rect.origin.x; + blitRect.origin.y = rect.origin.y; + blitRect.size.width = rect.size.width; + blitRect.size.height = rect.size.height; + + clipRect.origin.x = rect.origin.x; + clipRect.origin.y = frameRect.size.height - rect.origin.y - rect.size.height; + + clipRect.size.width = rect.size.width; + clipRect.size.height = rect.size.height; + + /* Blit dirty part of image */ + CGImageRef clippedImage = CGImageCreateWithImageInRect(fullImage, clipRect); + CGContextDrawImage(viewContext, blitRect, clippedImage); + CGImageRelease(clippedImage); + } else { + for (int n = 0; n < dirtyRectCount; n++) { + NSRect rect = dirtyRects[n]; + CGRect clipRect; + CGRect blitRect; + + blitRect.origin.x = rect.origin.x; + blitRect.origin.y = rect.origin.y; + blitRect.size.width = rect.size.width; + blitRect.size.height = rect.size.height; + + clipRect.origin.x = rect.origin.x; + clipRect.origin.y = frameRect.size.height - rect.origin.y - rect.size.height; + + clipRect.size.width = rect.size.width; + clipRect.size.height = rect.size.height; + + /* Blit dirty part of image */ + CGImageRef clippedImage = CGImageCreateWithImageInRect(fullImage, clipRect); + CGContextDrawImage(viewContext, blitRect, clippedImage); + CGImageRelease(clippedImage); + } + } + + CGImageRelease(fullImage); +} + +@end + #endif /* WITH_COCOA */ diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index 8f72836cca..dd1d269f8d 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -24,6 +24,7 @@ #include "../../openttd.h" #include "../../debug.h" +#include "../../rev.h" #include "cocoa_v.h" #include "cocoa_wnd.h" #include "../../string_func.h" @@ -345,6 +346,15 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; self->driver = drv; + + [ self setContentMinSize:NSMakeSize(64.0f, 64.0f) ]; + [ self setAcceptsMouseMovedEvents:YES ]; + + std::string caption = std::string{"OpenTTD "} + _openttd_revision; + NSString *nsscaption = [ [ NSString alloc ] initWithUTF8String:caption.c_str() ]; + [ self setTitle:nsscaption ]; + [ self setMiniwindowTitle:nsscaption ]; + [ nsscaption release ]; } return self; From f4bd54fedd40806ff34d884c0fa17e0b7403ede1 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 6 Feb 2021 02:49:09 +0100 Subject: [PATCH 054/122] Codechange: [OSX] Move mouse event handling to our NSView. --- src/video/cocoa/cocoa_v.h | 4 - src/video/cocoa/cocoa_v.mm | 52 +-------- src/video/cocoa/cocoa_wnd.h | 8 ++ src/video/cocoa/cocoa_wnd.mm | 147 +++++++++++++++++++++++- src/video/cocoa/event.mm | 214 +---------------------------------- 5 files changed, 154 insertions(+), 271 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index 4c53cdb8fa..0112f57515 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -78,11 +78,7 @@ protected: Dimension GetScreenSize() const override; private: - NSPoint GetMouseLocation(NSEvent *event); - bool MouseIsInsideView(NSPoint *pt); - CGPoint PrivateLocalToCG(NSPoint *p); bool PollEvent(); // In event.mm. - void MouseMovedEvent(int x, int y); // In event.mm. bool IsFullscreen(); void GameSizeChanged(); diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 88f461234f..07640a0f83 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -397,6 +397,7 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) [ this->window setContentView:this->cocoaview ]; [ this->cocoaview addSubview:draw_view ]; + [ this->window makeFirstResponder:this->cocoaview ]; [ draw_view release ]; [ this->window setColorSpace:[ NSColorSpace sRGBColorSpace ] ]; @@ -497,57 +498,6 @@ void VideoDriver_Cocoa::UpdatePalette(uint first_color, uint num_colors) this->num_dirty_rects = lengthof(this->dirty_rects); } -/** - * Convert local coordinate to window server (CoreGraphics) coordinate - * @param p local coordinates - * @return window driver coordinates - */ -CGPoint VideoDriver_Cocoa::PrivateLocalToCG(NSPoint *p) -{ - - p->y = this->window_height - p->y; - *p = [ this->cocoaview convertPoint:*p toView:nil ]; - *p = [ this->window convertRectToScreen:NSMakeRect(p->x, p->y, 0, 0) ].origin; - - p->y = NSScreen.screens[0].frame.size.height - p->y; - - CGPoint cgp; - cgp.x = p->x; - cgp.y = p->y; - - return cgp; -} - -/** - * Return the mouse location - * @param event UI event - * @return mouse location as NSPoint - */ -NSPoint VideoDriver_Cocoa::GetMouseLocation(NSEvent *event) -{ - NSPoint pt; - - if ( [ event window ] == nil) { - pt = [ this->cocoaview convertPoint:[ [ this->cocoaview window ] convertRectFromScreen:NSMakeRect([ event locationInWindow ].x, [ event locationInWindow ].y, 0, 0) ].origin fromView:nil ]; - } else { - pt = [ event locationInWindow ]; - } - - pt.y = this->window_height - pt.y; - - return pt; -} - -/** - * Return whether the mouse is within our view - * @param pt Mouse coordinates - * @return Whether mouse coordinates are within view - */ -bool VideoDriver_Cocoa::MouseIsInsideView(NSPoint *pt) -{ - return [ cocoaview mouse:*pt inRect:[ this->cocoaview bounds ] ]; -} - /** Clear buffer to opaque black. */ static void ClearWindowBuffer(uint32 *buffer, uint32 pitch, uint32 height) { diff --git a/src/video/cocoa/cocoa_wnd.h b/src/video/cocoa/cocoa_wnd.h index 449b457af1..98ed7df7ec 100644 --- a/src/video/cocoa/cocoa_wnd.h +++ b/src/video/cocoa/cocoa_wnd.h @@ -14,6 +14,13 @@ class VideoDriver_Cocoa; +/* Right Mouse Button Emulation enum */ +enum RightMouseButtonEmulationState { + RMBE_COMMAND = 0, + RMBE_CONTROL = 1, + RMBE_OFF = 2, +}; + extern NSString *OTTDMainLaunchGameEngine; /** Category of NSCursor to allow cursor showing/hiding */ @@ -34,6 +41,7 @@ extern NSString *OTTDMainLaunchGameEngine; /** Subclass of NSView to support mouse awareness and text input. */ @interface OTTD_CocoaView : NSView +- (NSPoint)mousePositionFromEvent:(NSEvent *)e; @end /** Delegate for our NSWindow to send ask for quit on close */ diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index dd1d269f8d..e9ea697057 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -27,6 +27,7 @@ #include "../../rev.h" #include "cocoa_v.h" #include "cocoa_wnd.h" +#include "../../settings_type.h" #include "../../string_func.h" #include "../../gfx_func.h" #include "../../window_func.h" @@ -348,7 +349,6 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel self->driver = drv; [ self setContentMinSize:NSMakeSize(64.0f, 64.0f) ]; - [ self setAcceptsMouseMovedEvents:YES ]; std::string caption = std::string{"OpenTTD "} + _openttd_revision; NSString *nsscaption = [ [ NSString alloc ] initWithUTF8String:caption.c_str() ]; @@ -417,7 +417,9 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel @end -@implementation OTTD_CocoaView +@implementation OTTD_CocoaView { + float _current_magnification; +} /** * Allow to handle events @@ -451,7 +453,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel - (void)viewDidMoveToWindow { /* Install mouse tracking area. */ - NSTrackingAreaOptions track_opt = NSTrackingInVisibleRect | NSTrackingActiveInActiveApp | NSTrackingMouseEnteredAndExited | NSTrackingCursorUpdate; + NSTrackingAreaOptions track_opt = NSTrackingInVisibleRect | NSTrackingActiveInActiveApp | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingCursorUpdate; NSTrackingArea *track = [ [ NSTrackingArea alloc ] initWithRect:[ self bounds ] options:track_opt owner:self userInfo:nil ]; [ self addTrackingArea:track ]; [ track release ]; @@ -472,6 +474,145 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel _cursor.in_window = false; } +/** + * Return the mouse location + * @param event UI event + * @return mouse location as NSPoint + */ +- (NSPoint)mousePositionFromEvent:(NSEvent *)e +{ + NSPoint pt = e.locationInWindow; + if ([ e window ] == nil) pt = [ self.window convertRectFromScreen:NSMakeRect(pt.x, pt.y, 0, 0) ].origin; + pt = [ self convertPoint:pt fromView:nil ]; + + pt.y = self.bounds.size.height - pt.y; + + return pt; +} + +- (void)internalMouseMoveEvent:(NSEvent *)event +{ + NSPoint pt = [ self mousePositionFromEvent:event ]; + + if (_cursor.UpdateCursorPosition(pt.x, pt.y, false) && [ NSApp isActive ]) { + /* Warping cursor when in foreground */ + NSPoint warp = [ self convertPoint:NSMakePoint(_cursor.pos.x, self.bounds.size.height - _cursor.pos.y) toView:nil ]; + warp = [ self.window convertRectToScreen:NSMakeRect(warp.x, warp.y, 0, 0) ].origin; + warp.y = NSScreen.screens[0].frame.size.height - warp.y; + + /* Do the actual warp */ + CGWarpMouseCursorPosition(NSPointToCGPoint(warp)); + /* this is the magic call that fixes cursor "freezing" after warp */ + CGAssociateMouseAndMouseCursorPosition(true); + } + HandleMouseEvents(); +} + +- (BOOL)emulateRightButton:(NSEvent *)event +{ + uint32 keymask = 0; + if (_settings_client.gui.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSCommandKeyMask; + if (_settings_client.gui.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSControlKeyMask; + + return (event.modifierFlags & keymask) != 0; +} + +- (void)mouseMoved:(NSEvent *)event +{ + [ self internalMouseMoveEvent:event ]; +} + +- (void)mouseDragged:(NSEvent *)event +{ + [ self internalMouseMoveEvent:event ]; +} +- (void)mouseDown:(NSEvent *)event +{ + if ([ self emulateRightButton:event ]) { + [ self rightMouseDown:event ]; + } else { + _left_button_down = true; + [ self internalMouseMoveEvent:event ]; + } +} +- (void)mouseUp:(NSEvent *)event +{ + if ([ self emulateRightButton:event ]) { + [ self rightMouseUp:event ]; + } else { + _left_button_down = false; + _left_button_clicked = false; + [ self internalMouseMoveEvent:event ]; + } +} + +- (void)rightMouseDragged:(NSEvent *)event +{ + [ self internalMouseMoveEvent:event ]; +} +- (void)rightMouseDown:(NSEvent *)event +{ + _right_button_down = true; + _right_button_clicked = true; + [ self internalMouseMoveEvent:event ]; +} +- (void)rightMouseUp:(NSEvent *)event +{ + _right_button_down = false; + [ self internalMouseMoveEvent:event ]; +} + +- (void)scrollWheel:(NSEvent *)event +{ + if ([ event deltaY ] > 0.0) { /* Scroll up */ + _cursor.wheel--; + } else if ([ event deltaY ] < 0.0) { /* Scroll down */ + _cursor.wheel++; + } /* else: deltaY was 0.0 and we don't want to do anything */ + + /* Update the scroll count for 2D scrolling */ + CGFloat deltaX; + CGFloat deltaY; + + /* Use precise scrolling-specific deltas if they're supported. */ + if ([ event respondsToSelector:@selector(hasPreciseScrollingDeltas) ]) { + /* No precise deltas indicates a scroll wheel is being used, so we don't want 2D scrolling. */ + if (![ event hasPreciseScrollingDeltas ]) return; + + deltaX = [ event scrollingDeltaX ] * 0.5f; + deltaY = [ event scrollingDeltaY ] * 0.5f; + } else { + deltaX = [ event deltaX ] * 5; + deltaY = [ event deltaY ] * 5; + } + + _cursor.h_wheel -= (int)(deltaX * _settings_client.gui.scrollwheel_multiplier); + _cursor.v_wheel -= (int)(deltaY * _settings_client.gui.scrollwheel_multiplier); +} + +- (void)magnifyWithEvent:(NSEvent *)event +{ + /* Pinch open or close gesture. */ + self->_current_magnification += [ event magnification ] * 5.0f; + + while (self->_current_magnification >= 1.0f) { + self->_current_magnification -= 1.0f; + _cursor.wheel--; + HandleMouseEvents(); + } + while (self->_current_magnification <= -1.0f) { + self->_current_magnification += 1.0f; + _cursor.wheel++; + HandleMouseEvents(); + } +} + +- (void)endGestureWithEvent:(NSEvent *)event +{ + /* Gesture ended. */ + self->_current_magnification = 0.0f; +} + /** Insert the given text at the given range. */ - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange diff --git a/src/video/cocoa/event.mm b/src/video/cocoa/event.mm index 00559e6bce..d6c4e5552c 100644 --- a/src/video/cocoa/event.mm +++ b/src/video/cocoa/event.mm @@ -27,6 +27,7 @@ #include "../../settings_type.h" #include "../../core/geometry_type.hpp" #include "cocoa_v.h" +#include "cocoa_wnd.h" #include "cocoa_keys.h" #include "../../blitter/factory.hpp" #include "../../gfx_func.h" @@ -48,18 +49,8 @@ */ -/* Right Mouse Button Emulation enum */ -enum RightMouseButtonEmulationState { - RMBE_COMMAND, - RMBE_CONTROL, - RMBE_OFF, -}; - - static unsigned int _current_mods; static bool _tab_is_down; -static bool _emulating_right_button; -static float _current_magnification; #ifdef _DEBUG static uint32 _tEvent; #endif @@ -300,47 +291,6 @@ static void QZ_DoUnsidedModifiers(unsigned int newMods) _current_mods = newMods; } -void VideoDriver_Cocoa::MouseMovedEvent(int x, int y) -{ - if (_cursor.UpdateCursorPosition(x, y, false) && [ NSApp isActive ]) { - /* Warping cursor when in foreground */ - NSPoint p = NSMakePoint(_cursor.pos.x, _cursor.pos.y); - CGPoint cgp = this->PrivateLocalToCG(&p); - - /* Do the actual warp */ - CGWarpMouseCursorPosition(cgp); - /* this is the magic call that fixes cursor "freezing" after warp */ - CGAssociateMouseAndMouseCursorPosition(true); - } - HandleMouseEvents(); -} - - -static void QZ_MouseButtonEvent(int button, BOOL down) -{ - switch (button) { - case 0: - if (down) { - _left_button_down = true; - } else { - _left_button_down = false; - _left_button_clicked = false; - } - HandleMouseEvents(); - break; - - case 1: - if (down) { - _right_button_down = true; - _right_button_clicked = true; - } else { - _right_button_down = false; - } - HandleMouseEvents(); - break; - } -} - @@ -365,111 +315,7 @@ bool VideoDriver_Cocoa::PollEvent() QZ_DoUnsidedModifiers( [ event modifierFlags ] ); NSString *chars; - NSPoint pt; switch ([ event type ]) { - case NSMouseMoved: - case NSOtherMouseDragged: - case NSLeftMouseDragged: - pt = this->GetMouseLocation(event); - if (!this->MouseIsInsideView(&pt) && !_emulating_right_button) { - [ NSApp sendEvent:event ]; - break; - } - - this->MouseMovedEvent((int)pt.x, (int)pt.y); - break; - - case NSRightMouseDragged: - pt = this->GetMouseLocation(event); - this->MouseMovedEvent((int)pt.x, (int)pt.y); - break; - - case NSLeftMouseDown: - { - uint32 keymask = 0; - if (_settings_client.gui.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSCommandKeyMask; - if (_settings_client.gui.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSControlKeyMask; - - pt = this->GetMouseLocation(event); - - if (!([ event modifierFlags ] & keymask) || !this->MouseIsInsideView(&pt)) { - [ NSApp sendEvent:event ]; - } - - this->MouseMovedEvent((int)pt.x, (int)pt.y); - - /* Right mouse button emulation */ - if ([ event modifierFlags ] & keymask) { - _emulating_right_button = true; - QZ_MouseButtonEvent(1, YES); - } else { - QZ_MouseButtonEvent(0, YES); - } - break; - } - case NSLeftMouseUp: - [ NSApp sendEvent:event ]; - - pt = this->GetMouseLocation(event); - - this->MouseMovedEvent((int)pt.x, (int)pt.y); - - /* Right mouse button emulation */ - if (_emulating_right_button) { - _emulating_right_button = false; - QZ_MouseButtonEvent(1, NO); - } else { - QZ_MouseButtonEvent(0, NO); - } - break; - - case NSRightMouseDown: - pt = this->GetMouseLocation(event); - if (!this->MouseIsInsideView(&pt)) { - [ NSApp sendEvent:event ]; - break; - } - - this->MouseMovedEvent((int)pt.x, (int)pt.y); - QZ_MouseButtonEvent(1, YES); - break; - - case NSRightMouseUp: - pt = this->GetMouseLocation(event); - if (!this->MouseIsInsideView(&pt)) { - [ NSApp sendEvent:event ]; - break; - } - - this->MouseMovedEvent((int)pt.x, (int)pt.y); - QZ_MouseButtonEvent(1, NO); - break; - -#if 0 - /* This is not needed since openttd currently only use two buttons */ - case NSOtherMouseDown: - pt = QZ_GetMouseLocation(event); - if (!QZ_MouseIsInsideView(&pt)) { - [ NSApp sendEvent:event ]; - break; - } - - this->MouseMovedEvent((int)pt.x, (int)pt.y); - QZ_MouseButtonEvent([ event buttonNumber ], YES); - break; - - case NSOtherMouseUp: - pt = QZ_GetMouseLocation(event); - if (!QZ_MouseIsInsideView(&pt)) { - [ NSApp sendEvent:event ]; - break; - } - - this->MouseMovedEvent((int)pt.x, (int)pt.y); - QZ_MouseButtonEvent([ event buttonNumber ], NO); - break; -#endif - case NSKeyDown: { /* Quit, hide and minimize */ switch ([ event keyCode ]) { @@ -513,64 +359,6 @@ bool VideoDriver_Cocoa::PollEvent() QZ_KeyEvent([ event keyCode ], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, NO); break; - case NSScrollWheel: - if ([ event deltaY ] > 0.0) { /* Scroll up */ - _cursor.wheel--; - } else if ([ event deltaY ] < 0.0) { /* Scroll down */ - _cursor.wheel++; - } /* else: deltaY was 0.0 and we don't want to do anything */ - - /* Update the scroll count for 2D scrolling */ - CGFloat deltaX; - CGFloat deltaY; - - /* Use precise scrolling-specific deltas if they're supported. */ - if ([event respondsToSelector:@selector(hasPreciseScrollingDeltas)]) { - /* No precise deltas indicates a scroll wheel is being used, so we don't want 2D scrolling. */ - if (![ event hasPreciseScrollingDeltas ]) break; - - deltaX = [ event scrollingDeltaX ] * 0.5f; - deltaY = [ event scrollingDeltaY ] * 0.5f; - } else { - deltaX = [ event deltaX ] * 5; - deltaY = [ event deltaY ] * 5; - } - - _cursor.h_wheel -= (int)(deltaX * _settings_client.gui.scrollwheel_multiplier); - _cursor.v_wheel -= (int)(deltaY * _settings_client.gui.scrollwheel_multiplier); - - break; - - case NSEventTypeMagnify: - /* Pinch open or close gesture. */ - _current_magnification += [ event magnification ] * 5.0f; - - while (_current_magnification >= 1.0f) { - _current_magnification -= 1.0f; - _cursor.wheel--; - HandleMouseEvents(); - } - while (_current_magnification <= -1.0f) { - _current_magnification += 1.0f; - _cursor.wheel++; - HandleMouseEvents(); - } - break; - - case NSEventTypeEndGesture: - /* Gesture ended. */ - _current_magnification = 0.0f; - break; - - case NSCursorUpdate: - case NSMouseEntered: - case NSMouseExited: - /* Catch these events if the cursor is dragging. During dragging, we reset - * the mouse position programmatically, which would trigger OS X to show - * the default arrow cursor if the events are propagated. */ - if (_cursor.fix_at) break; - FALLTHROUGH; - default: [ NSApp sendEvent:event ]; } From 063b90b97db14e19a339a3970eb33c6ba6e6d37f Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 6 Feb 2021 20:03:33 +0100 Subject: [PATCH 055/122] Codechange: [OSX] Move key event handling to our view. --- src/video/cocoa/cocoa_keys.h | 122 ++++++++++++++ src/video/cocoa/cocoa_wnd.mm | 173 ++++++++++++++++++++ src/video/cocoa/event.mm | 297 +---------------------------------- 3 files changed, 302 insertions(+), 290 deletions(-) diff --git a/src/video/cocoa/cocoa_keys.h b/src/video/cocoa/cocoa_keys.h index def145d242..e09fe3c8a7 100644 --- a/src/video/cocoa/cocoa_keys.h +++ b/src/video/cocoa/cocoa_keys.h @@ -131,4 +131,126 @@ #define QZ_IBOOK_DOWN 0x3D #define QZ_IBOOK_UP 0x3E + +struct VkMapping { + unsigned short vk_from; + byte map_to; +}; + +#define AS(x, z) {x, z} + +static const VkMapping _vk_mapping[] = { + AS(QZ_BACKQUOTE, WKC_BACKQUOTE), // key left of '1' + AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode + + /* Pageup stuff + up/down */ + AS(QZ_PAGEUP, WKC_PAGEUP), + AS(QZ_PAGEDOWN, WKC_PAGEDOWN), + + AS(QZ_UP, WKC_UP), + AS(QZ_DOWN, WKC_DOWN), + AS(QZ_LEFT, WKC_LEFT), + AS(QZ_RIGHT, WKC_RIGHT), + + AS(QZ_HOME, WKC_HOME), + AS(QZ_END, WKC_END), + + AS(QZ_INSERT, WKC_INSERT), + AS(QZ_DELETE, WKC_DELETE), + + /* Letters. QZ_[a-z] is not in numerical order so we can't use AM(...) */ + AS(QZ_a, 'A'), + AS(QZ_b, 'B'), + AS(QZ_c, 'C'), + AS(QZ_d, 'D'), + AS(QZ_e, 'E'), + AS(QZ_f, 'F'), + AS(QZ_g, 'G'), + AS(QZ_h, 'H'), + AS(QZ_i, 'I'), + AS(QZ_j, 'J'), + AS(QZ_k, 'K'), + AS(QZ_l, 'L'), + AS(QZ_m, 'M'), + AS(QZ_n, 'N'), + AS(QZ_o, 'O'), + AS(QZ_p, 'P'), + AS(QZ_q, 'Q'), + AS(QZ_r, 'R'), + AS(QZ_s, 'S'), + AS(QZ_t, 'T'), + AS(QZ_u, 'U'), + AS(QZ_v, 'V'), + AS(QZ_w, 'W'), + AS(QZ_x, 'X'), + AS(QZ_y, 'Y'), + AS(QZ_z, 'Z'), + /* Same thing for digits */ + AS(QZ_0, '0'), + AS(QZ_1, '1'), + AS(QZ_2, '2'), + AS(QZ_3, '3'), + AS(QZ_4, '4'), + AS(QZ_5, '5'), + AS(QZ_6, '6'), + AS(QZ_7, '7'), + AS(QZ_8, '8'), + AS(QZ_9, '9'), + + AS(QZ_ESCAPE, WKC_ESC), + AS(QZ_PAUSE, WKC_PAUSE), + AS(QZ_BACKSPACE, WKC_BACKSPACE), + + AS(QZ_SPACE, WKC_SPACE), + AS(QZ_RETURN, WKC_RETURN), + AS(QZ_TAB, WKC_TAB), + + /* Function keys */ + AS(QZ_F1, WKC_F1), + AS(QZ_F2, WKC_F2), + AS(QZ_F3, WKC_F3), + AS(QZ_F4, WKC_F4), + AS(QZ_F5, WKC_F5), + AS(QZ_F6, WKC_F6), + AS(QZ_F7, WKC_F7), + AS(QZ_F8, WKC_F8), + AS(QZ_F9, WKC_F9), + AS(QZ_F10, WKC_F10), + AS(QZ_F11, WKC_F11), + AS(QZ_F12, WKC_F12), + + /* Numeric part */ + AS(QZ_KP0, '0'), + AS(QZ_KP1, '1'), + AS(QZ_KP2, '2'), + AS(QZ_KP3, '3'), + AS(QZ_KP4, '4'), + AS(QZ_KP5, '5'), + AS(QZ_KP6, '6'), + AS(QZ_KP7, '7'), + AS(QZ_KP8, '8'), + AS(QZ_KP9, '9'), + AS(QZ_KP_DIVIDE, WKC_NUM_DIV), + AS(QZ_KP_MULTIPLY, WKC_NUM_MUL), + AS(QZ_KP_MINUS, WKC_NUM_MINUS), + AS(QZ_KP_PLUS, WKC_NUM_PLUS), + AS(QZ_KP_ENTER, WKC_NUM_ENTER), + AS(QZ_KP_PERIOD, WKC_NUM_DECIMAL), + + /* Other non-letter keys */ + AS(QZ_SLASH, WKC_SLASH), + AS(QZ_SEMICOLON, WKC_SEMICOLON), + AS(QZ_EQUALS, WKC_EQUALS), + AS(QZ_LEFTBRACKET, WKC_L_BRACKET), + AS(QZ_BACKSLASH, WKC_BACKSLASH), + AS(QZ_RIGHTBRACKET, WKC_R_BRACKET), + + AS(QZ_QUOTE, WKC_SINGLEQUOTE), + AS(QZ_COMMA, WKC_COMMA), + AS(QZ_MINUS, WKC_MINUS), + AS(QZ_PERIOD, WKC_PERIOD) +}; + +#undef AS + #endif diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index e9ea697057..2755b26ff4 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -34,6 +34,10 @@ #include "../../window_gui.h" +/* Table data for key mapping. */ +#include "cocoa_keys.h" + + /** * Important notice regarding all modifications!!!!!!! * There are certain limitations because the file is objective C++. @@ -47,6 +51,8 @@ NSString *OTTDMainLaunchGameEngine = @"ottdmain_launch_game_engine"; +bool _tab_is_down; + static bool _cocoa_video_dialog = false; static OTTDMain *_ottd_main; @@ -89,6 +95,31 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) return str; } +/** + * Convert a NSString to an UTF-32 encoded string. + * @param s String to convert. + * @return Vector of UTF-32 characters. + */ +static std::vector NSStringToUTF32(NSString *s) +{ + std::vector unicode_str; + + unichar lead = 0; + for (NSUInteger i = 0; i < s.length; i++) { + unichar c = [ s characterAtIndex:i ]; + if (Utf16IsLeadSurrogate(c)) { + lead = c; + continue; + } else if (Utf16IsTrailSurrogate(c)) { + if (lead != 0) unicode_str.push_back(Utf16DecodeSurrogate(lead, c)); + } else { + unicode_str.push_back(c); + } + } + + return unicode_str; +} + /** * The main class of the application, the application's delegate. @@ -419,6 +450,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel @implementation OTTD_CocoaView { float _current_magnification; + NSUInteger _current_mods; } /** @@ -614,6 +646,147 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel } +- (BOOL)internalHandleKeycode:(unsigned short)keycode unicode:(WChar)unicode pressed:(BOOL)down modifiers:(NSUInteger)modifiers +{ + switch (keycode) { + case QZ_UP: SB(_dirkeys, 1, 1, down); break; + case QZ_DOWN: SB(_dirkeys, 3, 1, down); break; + case QZ_LEFT: SB(_dirkeys, 0, 1, down); break; + case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break; + + case QZ_TAB: _tab_is_down = down; break; + + case QZ_RETURN: + case QZ_f: + if (down && (modifiers & NSCommandKeyMask)) { + VideoDriver::GetInstance()->ToggleFullscreen(!_fullscreen); + } + break; + + case QZ_v: + if (down && EditBoxInGlobalFocus() && (modifiers & (NSCommandKeyMask | NSControlKeyMask))) { + HandleKeypress(WKC_CTRL | 'V', unicode); + } + break; + case QZ_u: + if (down && EditBoxInGlobalFocus() && (modifiers & (NSCommandKeyMask | NSControlKeyMask))) { + HandleKeypress(WKC_CTRL | 'U', unicode); + } + break; + } + + BOOL interpret_keys = YES; + if (down) { + /* Map keycode to OTTD code. */ + auto vk = std::find_if(std::begin(_vk_mapping), std::end(_vk_mapping), [=](const VkMapping &m) { return m.vk_from == keycode; }); + uint32 pressed_key = vk != std::end(_vk_mapping) ? vk->map_to : 0; + + if (modifiers & NSShiftKeyMask) pressed_key |= WKC_SHIFT; + if (modifiers & NSControlKeyMask) pressed_key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META); + if (modifiers & NSAlternateKeyMask) pressed_key |= WKC_ALT; + if (modifiers & NSCommandKeyMask) pressed_key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL); + + static bool console = false; + + /* The second backquote may have a character, which we don't want to interpret. */ + if (pressed_key == WKC_BACKQUOTE && (console || unicode == 0)) { + if (!console) { + /* Backquote is a dead key, require a double press for hotkey behaviour (i.e. console). */ + console = true; + return YES; + } else { + /* Second backquote, don't interpret as text input. */ + interpret_keys = NO; + } + } + console = false; + + /* Don't handle normal characters if an edit box has the focus. */ + if (!EditBoxInGlobalFocus() || IsInsideMM(pressed_key & ~WKC_SPECIAL_KEYS, WKC_F1, WKC_PAUSE + 1)) { + HandleKeypress(pressed_key, unicode); + } + DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, pressed_key); + } else { + DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode); + } + + return interpret_keys; +} + +- (void)keyDown:(NSEvent *)event +{ + /* Quit, hide and minimize */ + switch (event.keyCode) { + case QZ_q: + case QZ_h: + case QZ_m: + if (event.modifierFlags & NSCommandKeyMask) { + [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ]; + } + break; + } + + /* Convert UTF-16 characters to UCS-4 chars. */ + std::vector unicode_str = NSStringToUTF32([ event characters ]); + if (unicode_str.empty()) unicode_str.push_back(0); + + if (EditBoxInGlobalFocus()) { + if ([ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:YES modifiers:event.modifierFlags ]) { + [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ]; + } + } else { + [ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:YES modifiers:event.modifierFlags ]; + for (size_t i = 1; i < unicode_str.size(); i++) { + [ self internalHandleKeycode:0 unicode:unicode_str[i] pressed:YES modifiers:event.modifierFlags ]; + } + } +} + +- (void)keyUp:(NSEvent *)event +{ + /* Quit, hide and minimize */ + switch (event.keyCode) { + case QZ_q: + case QZ_h: + case QZ_m: + if (event.modifierFlags & NSCommandKeyMask) { + [ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ]; + } + break; + } + + /* Convert UTF-16 characters to UCS-4 chars. */ + std::vector unicode_str = NSStringToUTF32([ event characters ]); + if (unicode_str.empty()) unicode_str.push_back(0); + + [ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:NO modifiers:event.modifierFlags ]; +} + +- (void)flagsChanged:(NSEvent *)event +{ + const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA }; + + NSUInteger newMods = event.modifierFlags; + if (self->_current_mods == newMods) return; + + /* Iterate through the bits, testing each against the current modifiers */ + for (unsigned int i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) { + unsigned int currentMask, newMask; + + currentMask = self->_current_mods & bit; + newMask = newMods & bit; + + if (currentMask && currentMask != newMask) { // modifier up event + [ self internalHandleKeycode:mapping[i] unicode:0 pressed:NO modifiers:newMods ]; + } else if (newMask && currentMask != newMask) { // modifier down event + [ self internalHandleKeycode:mapping[i] unicode:0 pressed:YES modifiers:newMods ]; + } + } + + _current_mods = newMods; +} + + /** Insert the given text at the given range. */ - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { diff --git a/src/video/cocoa/event.mm b/src/video/cocoa/event.mm index d6c4e5552c..17d4c437dc 100644 --- a/src/video/cocoa/event.mm +++ b/src/video/cocoa/event.mm @@ -28,7 +28,6 @@ #include "../../core/geometry_type.hpp" #include "cocoa_v.h" #include "cocoa_wnd.h" -#include "cocoa_keys.h" #include "../../blitter/factory.hpp" #include "../../gfx_func.h" #include "../../network/network.h" @@ -48,9 +47,8 @@ * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information. */ +extern bool _tab_is_down; -static unsigned int _current_mods; -static bool _tab_is_down; #ifdef _DEBUG static uint32 _tEvent; #endif @@ -64,236 +62,6 @@ static uint32 GetTick() return tim.tv_usec / 1000 + tim.tv_sec * 1000; } -struct VkMapping { - unsigned short vk_from; - byte map_to; -}; - -#define AS(x, z) {x, z} - -static const VkMapping _vk_mapping[] = { - AS(QZ_BACKQUOTE, WKC_BACKQUOTE), // key left of '1' - AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode - - /* Pageup stuff + up/down */ - AS(QZ_PAGEUP, WKC_PAGEUP), - AS(QZ_PAGEDOWN, WKC_PAGEDOWN), - - AS(QZ_UP, WKC_UP), - AS(QZ_DOWN, WKC_DOWN), - AS(QZ_LEFT, WKC_LEFT), - AS(QZ_RIGHT, WKC_RIGHT), - - AS(QZ_HOME, WKC_HOME), - AS(QZ_END, WKC_END), - - AS(QZ_INSERT, WKC_INSERT), - AS(QZ_DELETE, WKC_DELETE), - - /* Letters. QZ_[a-z] is not in numerical order so we can't use AM(...) */ - AS(QZ_a, 'A'), - AS(QZ_b, 'B'), - AS(QZ_c, 'C'), - AS(QZ_d, 'D'), - AS(QZ_e, 'E'), - AS(QZ_f, 'F'), - AS(QZ_g, 'G'), - AS(QZ_h, 'H'), - AS(QZ_i, 'I'), - AS(QZ_j, 'J'), - AS(QZ_k, 'K'), - AS(QZ_l, 'L'), - AS(QZ_m, 'M'), - AS(QZ_n, 'N'), - AS(QZ_o, 'O'), - AS(QZ_p, 'P'), - AS(QZ_q, 'Q'), - AS(QZ_r, 'R'), - AS(QZ_s, 'S'), - AS(QZ_t, 'T'), - AS(QZ_u, 'U'), - AS(QZ_v, 'V'), - AS(QZ_w, 'W'), - AS(QZ_x, 'X'), - AS(QZ_y, 'Y'), - AS(QZ_z, 'Z'), - /* Same thing for digits */ - AS(QZ_0, '0'), - AS(QZ_1, '1'), - AS(QZ_2, '2'), - AS(QZ_3, '3'), - AS(QZ_4, '4'), - AS(QZ_5, '5'), - AS(QZ_6, '6'), - AS(QZ_7, '7'), - AS(QZ_8, '8'), - AS(QZ_9, '9'), - - AS(QZ_ESCAPE, WKC_ESC), - AS(QZ_PAUSE, WKC_PAUSE), - AS(QZ_BACKSPACE, WKC_BACKSPACE), - - AS(QZ_SPACE, WKC_SPACE), - AS(QZ_RETURN, WKC_RETURN), - AS(QZ_TAB, WKC_TAB), - - /* Function keys */ - AS(QZ_F1, WKC_F1), - AS(QZ_F2, WKC_F2), - AS(QZ_F3, WKC_F3), - AS(QZ_F4, WKC_F4), - AS(QZ_F5, WKC_F5), - AS(QZ_F6, WKC_F6), - AS(QZ_F7, WKC_F7), - AS(QZ_F8, WKC_F8), - AS(QZ_F9, WKC_F9), - AS(QZ_F10, WKC_F10), - AS(QZ_F11, WKC_F11), - AS(QZ_F12, WKC_F12), - - /* Numeric part */ - AS(QZ_KP0, '0'), - AS(QZ_KP1, '1'), - AS(QZ_KP2, '2'), - AS(QZ_KP3, '3'), - AS(QZ_KP4, '4'), - AS(QZ_KP5, '5'), - AS(QZ_KP6, '6'), - AS(QZ_KP7, '7'), - AS(QZ_KP8, '8'), - AS(QZ_KP9, '9'), - AS(QZ_KP_DIVIDE, WKC_NUM_DIV), - AS(QZ_KP_MULTIPLY, WKC_NUM_MUL), - AS(QZ_KP_MINUS, WKC_NUM_MINUS), - AS(QZ_KP_PLUS, WKC_NUM_PLUS), - AS(QZ_KP_ENTER, WKC_NUM_ENTER), - AS(QZ_KP_PERIOD, WKC_NUM_DECIMAL), - - /* Other non-letter keys */ - AS(QZ_SLASH, WKC_SLASH), - AS(QZ_SEMICOLON, WKC_SEMICOLON), - AS(QZ_EQUALS, WKC_EQUALS), - AS(QZ_LEFTBRACKET, WKC_L_BRACKET), - AS(QZ_BACKSLASH, WKC_BACKSLASH), - AS(QZ_RIGHTBRACKET, WKC_R_BRACKET), - - AS(QZ_QUOTE, WKC_SINGLEQUOTE), - AS(QZ_COMMA, WKC_COMMA), - AS(QZ_MINUS, WKC_MINUS), - AS(QZ_PERIOD, WKC_PERIOD) -}; - - -static uint32 QZ_MapKey(unsigned short sym) -{ - uint32 key = 0; - - for (const VkMapping *map = _vk_mapping; map != endof(_vk_mapping); ++map) { - if (sym == map->vk_from) { - key = map->map_to; - break; - } - } - - if (_current_mods & NSShiftKeyMask) key |= WKC_SHIFT; - if (_current_mods & NSControlKeyMask) key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META); - if (_current_mods & NSAlternateKeyMask) key |= WKC_ALT; - if (_current_mods & NSCommandKeyMask) key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL); - - return key; -} - -static bool QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down) -{ - bool interpret_keys = true; - - switch (keycode) { - case QZ_UP: SB(_dirkeys, 1, 1, down); break; - case QZ_DOWN: SB(_dirkeys, 3, 1, down); break; - case QZ_LEFT: SB(_dirkeys, 0, 1, down); break; - case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break; - - case QZ_TAB: _tab_is_down = down; break; - - case QZ_RETURN: - case QZ_f: - if (down && (_current_mods & NSCommandKeyMask)) { - VideoDriver::GetInstance()->ToggleFullscreen(!_fullscreen); - } - break; - - case QZ_v: - if (down && EditBoxInGlobalFocus() && (_current_mods & (NSCommandKeyMask | NSControlKeyMask))) { - HandleKeypress(WKC_CTRL | 'V', unicode); - } - break; - case QZ_u: - if (down && EditBoxInGlobalFocus() && (_current_mods & (NSCommandKeyMask | NSControlKeyMask))) { - HandleKeypress(WKC_CTRL | 'U', unicode); - } - break; - } - - if (down) { - uint32 pressed_key = QZ_MapKey(keycode); - - static bool console = false; - - /* The second backquote may have a character, which we don't want to interpret. */ - if (pressed_key == WKC_BACKQUOTE && (console || unicode == 0)) { - if (!console) { - /* Backquote is a dead key, require a double press for hotkey behaviour (i.e. console). */ - console = true; - return true; - } else { - /* Second backquote, don't interpret as text input. */ - interpret_keys = false; - } - } - console = false; - - /* Don't handle normal characters if an edit box has the focus. */ - if (!EditBoxInGlobalFocus() || IsInsideMM(pressed_key & ~WKC_SPECIAL_KEYS, WKC_F1, WKC_PAUSE + 1)) { - HandleKeypress(pressed_key, unicode); - } - DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, pressed_key); - } else { - DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode); - } - - return interpret_keys; -} - -static void QZ_DoUnsidedModifiers(unsigned int newMods) -{ - const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA }; - - if (_current_mods == newMods) return; - - /* Iterate through the bits, testing each against the current modifiers */ - for (unsigned int i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) { - unsigned int currentMask, newMask; - - currentMask = _current_mods & bit; - newMask = newMods & bit; - - if (currentMask && currentMask != newMask) { // modifier up event - /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */ - if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, YES); - QZ_KeyEvent(mapping[i], 0, NO); - } else if (newMask && currentMask != newMask) { // modifier down event - QZ_KeyEvent(mapping[i], 0, YES); - /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */ - if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, NO); - } - } - - _current_mods = newMods; -} - - - - bool VideoDriver_Cocoa::PollEvent() { #ifdef _DEBUG @@ -307,61 +75,8 @@ bool VideoDriver_Cocoa::PollEvent() #endif if (event == nil) return false; - if (!this->active) { - [ NSApp sendEvent:event ]; - return true; - } - - QZ_DoUnsidedModifiers( [ event modifierFlags ] ); - - NSString *chars; - switch ([ event type ]) { - case NSKeyDown: { - /* Quit, hide and minimize */ - switch ([ event keyCode ]) { - case QZ_q: - case QZ_h: - case QZ_m: - if ([ event modifierFlags ] & NSCommandKeyMask) { - [ NSApp sendEvent:event ]; - } - break; - } - chars = [ event characters ]; - unsigned short unicode = [ chars length ] > 0 ? [ chars characterAtIndex:0 ] : 0; - if (EditBoxInGlobalFocus()) { - if (QZ_KeyEvent([ event keyCode ], unicode, YES)) { - [ this->cocoaview interpretKeyEvents:[ NSArray arrayWithObject:event ] ]; - } - } else { - QZ_KeyEvent([ event keyCode ], unicode, YES); - for (uint i = 1; i < [ chars length ]; i++) { - QZ_KeyEvent(0, [ chars characterAtIndex:i ], YES); - } - } - break; - } - - case NSKeyUp: - /* Quit, hide and minimize */ - switch ([ event keyCode ]) { - case QZ_q: - case QZ_h: - case QZ_m: - if ([ event modifierFlags ] & NSCommandKeyMask) { - [ NSApp sendEvent:event ]; - } - break; - } - - chars = [ event characters ]; - QZ_KeyEvent([ event keyCode ], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, NO); - break; - - default: - [ NSApp sendEvent:event ]; - } + [ NSApp sendEvent:event ]; return true; } @@ -410,8 +125,10 @@ void VideoDriver_Cocoa::GameLoop() break; } + NSUInteger cur_mods = [ NSEvent modifierFlags ]; + #if defined(_DEBUG) - if (_current_mods & NSShiftKeyMask) + if (cur_mods & NSShiftKeyMask) #else if (_tab_is_down) #endif @@ -429,8 +146,8 @@ void VideoDriver_Cocoa::GameLoop() bool old_ctrl_pressed = _ctrl_pressed; - _ctrl_pressed = !!(_current_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)); - _shift_pressed = !!(_current_mods & NSShiftKeyMask); + _ctrl_pressed = !!(cur_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)); + _shift_pressed = !!(cur_mods & NSShiftKeyMask); if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); From 23389e949169ee65ca2b53cb09c11f6d4ce2bd67 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 6 Feb 2021 20:09:12 +0100 Subject: [PATCH 056/122] Remove: [OSX] Startup splash screen. It wasn't displayed anyway as it was never copied to the bundle. --- src/os/macosx/CMakeLists.txt | 2 - src/os/macosx/splash.cpp | 185 ----------------------------------- src/os/macosx/splash.h | 17 ---- src/video/cocoa/event.mm | 19 ---- 4 files changed, 223 deletions(-) delete mode 100644 src/os/macosx/splash.cpp delete mode 100644 src/os/macosx/splash.h diff --git a/src/os/macosx/CMakeLists.txt b/src/os/macosx/CMakeLists.txt index 31f6af7d49..6b4f2f279d 100644 --- a/src/os/macosx/CMakeLists.txt +++ b/src/os/macosx/CMakeLists.txt @@ -4,8 +4,6 @@ add_files( macos.h macos.mm osx_stdafx.h - splash.cpp - splash.h string_osx.cpp string_osx.h CONDITION APPLE diff --git a/src/os/macosx/splash.cpp b/src/os/macosx/splash.cpp deleted file mode 100644 index ac2f86425b..0000000000 --- a/src/os/macosx/splash.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * This file is part of OpenTTD. - * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. - * OpenTTD 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 OpenTTD. If not, see . - */ - -/** @file splash.cpp Splash screen support for OSX. */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../debug.h" -#include "../../gfx_func.h" -#include "../../fileio_func.h" -#include "../../blitter/factory.hpp" -#include "../../core/mem_func.hpp" - -#include "splash.h" - -#ifdef WITH_PNG - -#include - -#include "../../safeguards.h" - -/** - * Handle pnglib error. - * - * @param png_ptr Pointer to png struct. - * @param message Error message text. - */ -static void PNGAPI png_my_error(png_structp png_ptr, png_const_charp message) -{ - DEBUG(misc, 0, "[libpng] error: %s - %s", message, (char *)png_get_error_ptr(png_ptr)); - longjmp(png_jmpbuf(png_ptr), 1); -} - -/** - * Handle warning in pnglib. - * - * @param png_ptr Pointer to png struct. - * @param message Warning message text. - */ -static void PNGAPI png_my_warning(png_structp png_ptr, png_const_charp message) -{ - DEBUG(misc, 1, "[libpng] warning: %s - %s", message, (char *)png_get_error_ptr(png_ptr)); -} - -/** - * Display a splash image shown on startup (WITH_PNG). - */ -void DisplaySplashImage() -{ - FILE *f = FioFOpenFile(SPLASH_IMAGE_FILE, "r", BASESET_DIR); - if (f == nullptr) return; - - png_byte header[8]; - fread(header, sizeof(png_byte), 8, f); - if (png_sig_cmp(header, 0, 8) != 0) { - fclose(f); - return; - } - - png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp) nullptr, png_my_error, png_my_warning); - - if (png_ptr == nullptr) { - fclose(f); - return; - } - - png_infop info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == nullptr) { - png_destroy_read_struct(&png_ptr, (png_infopp)nullptr, (png_infopp)nullptr); - fclose(f); - return; - } - - png_infop end_info = png_create_info_struct(png_ptr); - if (end_info == nullptr) { - png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)nullptr); - fclose(f); - return; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - fclose(f); - return; - } - - png_init_io(png_ptr, f); - png_set_sig_bytes(png_ptr, 8); - - png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); - - uint width = png_get_image_width(png_ptr, info_ptr); - uint height = png_get_image_height(png_ptr, info_ptr); - uint bit_depth = png_get_bit_depth(png_ptr, info_ptr); - uint color_type = png_get_color_type(png_ptr, info_ptr); - - if (color_type != PNG_COLOR_TYPE_PALETTE || bit_depth != 8) { - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - fclose(f); - return; - } - - if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_PLTE)) { - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - fclose(f); - return; - } - - png_colorp palette; - int num_palette; - png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); - - png_bytep *row_pointers = png_get_rows(png_ptr, info_ptr); - - if (width > (uint) _screen.width) width = _screen.width; - if (height > (uint) _screen.height) height = _screen.height; - - uint xoff = (_screen.width - width) / 2; - uint yoff = (_screen.height - height) / 2; - - switch (BlitterFactory::GetCurrentBlitter()->GetScreenDepth()) { - case 8: { - uint8 *dst_ptr = (uint8 *)_screen.dst_ptr; - /* Initialize buffer */ - MemSetT(dst_ptr, 0xff, _screen.pitch * _screen.height); - - for (uint y = 0; y < height; y++) { - uint8 *src = row_pointers[y]; - uint8 *dst = dst_ptr + (yoff + y) * _screen.pitch + xoff; - - memcpy(dst, src, width); - } - - for (int i = 0; i < num_palette; i++) { - _cur_palette.palette[i].a = i == 0 ? 0 : 0xff; - _cur_palette.palette[i].r = palette[i].red; - _cur_palette.palette[i].g = palette[i].green; - _cur_palette.palette[i].b = palette[i].blue; - } - - _cur_palette.palette[0xff].a = 0xff; - _cur_palette.palette[0xff].r = 0; - _cur_palette.palette[0xff].g = 0; - _cur_palette.palette[0xff].b = 0; - - _cur_palette.first_dirty = 0; - _cur_palette.count_dirty = 256; - break; - } - case 32: { - uint32 *dst_ptr = (uint32 *)_screen.dst_ptr; - /* Initialize buffer */ - MemSetT(dst_ptr, 0, _screen.pitch * _screen.height); - - for (uint y = 0; y < height; y++) { - uint8 *src = row_pointers[y]; - uint32 *dst = dst_ptr + (yoff + y) * _screen.pitch + xoff; - - for (uint x = 0; x < width; x++) { - dst[x] = palette[src[x]].blue | (palette[src[x]].green << 8) | (palette[src[x]].red << 16) | 0xff000000; - } - } - break; - } - } - - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - fclose(f); - return; -} - - - -#else /* WITH_PNG */ - -/** - * Empty 'Display a splash image' routine (WITHOUT_PNG). - */ -void DisplaySplashImage() {} - -#endif /* WITH_PNG */ diff --git a/src/os/macosx/splash.h b/src/os/macosx/splash.h deleted file mode 100644 index 8ddb638578..0000000000 --- a/src/os/macosx/splash.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * This file is part of OpenTTD. - * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. - * OpenTTD 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 OpenTTD. If not, see . - */ - -/** @file splash.h Functions to support splash screens for OSX. */ - -#ifndef SPLASH_H -#define SPLASH_H - -#define SPLASH_IMAGE_FILE "splash.png" - -void DisplaySplashImage(); - -#endif /* SPLASH_H */ diff --git a/src/video/cocoa/event.mm b/src/video/cocoa/event.mm index 17d4c437dc..c7ab70538d 100644 --- a/src/video/cocoa/event.mm +++ b/src/video/cocoa/event.mm @@ -23,7 +23,6 @@ #include "../../openttd.h" #include "../../debug.h" -#include "../../os/macosx/splash.h" #include "../../settings_type.h" #include "../../core/geometry_type.hpp" #include "cocoa_v.h" @@ -93,24 +92,6 @@ void VideoDriver_Cocoa::GameLoop() uint32 st = 0; #endif - DisplaySplashImage(); - this->CheckPaletteAnim(); - this->Draw(true); - CSleep(1); - - for (int i = 0; i < 2; i++) ::GameLoop(); - - UpdateWindows(); - this->CheckPaletteAnim(); - this->Draw(); - CSleep(1); - - /* Set the proper OpenTTD palette which got spoilt by the splash - * image when using 8bpp blitter */ - GfxInitPalettes(); - this->CheckPaletteAnim(); - this->Draw(true); - for (;;) { @autoreleasepool { From 94b76ce9a4ec8280f65ff289c637ea739514fb9d Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 6 Feb 2021 20:22:37 +0100 Subject: [PATCH 057/122] Cleanup: [OSX] Move event loop into video driver file. --- src/video/cocoa/CMakeLists.txt | 1 - src/video/cocoa/cocoa_v.h | 2 +- src/video/cocoa/cocoa_v.mm | 123 +++++++++++++++++++++++- src/video/cocoa/event.mm | 165 --------------------------------- 4 files changed, 122 insertions(+), 169 deletions(-) delete mode 100644 src/video/cocoa/event.mm diff --git a/src/video/cocoa/CMakeLists.txt b/src/video/cocoa/CMakeLists.txt index 8bcc6b2e03..968a98a91d 100644 --- a/src/video/cocoa/CMakeLists.txt +++ b/src/video/cocoa/CMakeLists.txt @@ -4,6 +4,5 @@ add_files( cocoa_v.mm cocoa_wnd.h cocoa_wnd.mm - event.mm CONDITION APPLE ) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index 0112f57515..8ebf5ba6e1 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -70,7 +70,7 @@ public: /* --- The following methods should be private, but can't be due to Obj-C limitations. --- */ /** Main game loop. */ - void GameLoop(); // In event.mm. + void GameLoop(); void AllocateBackingStore(); diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 07640a0f83..c35ee9b7dd 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -27,16 +27,21 @@ #include "../../openttd.h" #include "../../debug.h" #include "../../core/geometry_type.hpp" +#include "../../core/math_func.hpp" #include "cocoa_v.h" #include "cocoa_wnd.h" #include "../../blitter/factory.hpp" +#include "../../framerate_type.h" +#include "../../network/network.h" #include "../../gfx_func.h" +#include "../../thread.h" +#include "../../core/random_func.hpp" +#include "../../settings_type.h" #include "../../window_func.h" #include "../../window_gui.h" -#include "../../core/math_func.hpp" -#include "../../framerate_type.h" #import /* for MAXPATHLEN */ +#import /* gettimeofday */ /** * Important notice regarding all modifications!!!!!!! @@ -55,6 +60,12 @@ bool _cocoa_video_started = false; +extern bool _tab_is_down; + +#ifdef _DEBUG +static uint32 _tEvent; +#endif + /** List of common display/window sizes. */ static const Dimension _default_resolutions[] = { @@ -75,6 +86,15 @@ static const Dimension _default_resolutions[] = { static FVideoDriver_Cocoa iFVideoDriver_Cocoa; +static uint32 GetTick() +{ + struct timeval tim; + + gettimeofday(&tim, NULL); + return tim.tv_usec / 1000 + tim.tv_sec * 1000; +} + + /* Subclass of OTTD_CocoaView to fix Quartz rendering */ @interface OTTD_QuartzView : NSView { VideoDriver_Cocoa *driver; @@ -578,6 +598,105 @@ void VideoDriver_Cocoa::CheckPaletteAnim() } +bool VideoDriver_Cocoa::PollEvent() +{ +#ifdef _DEBUG + uint32 et0 = GetTick(); +#endif + NSEvent *event = [ NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[ NSDate distantPast ] inMode:NSDefaultRunLoopMode dequeue:YES ]; +#ifdef _DEBUG + _tEvent += GetTick() - et0; +#endif + + if (event == nil) return false; + + [ NSApp sendEvent:event ]; + + return true; +} + + +void VideoDriver_Cocoa::GameLoop() +{ + uint32 cur_ticks = GetTick(); + uint32 last_cur_ticks = cur_ticks; + uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK; + +#ifdef _DEBUG + uint32 et0 = GetTick(); + uint32 st = 0; +#endif + + for (;;) { + @autoreleasepool { + + uint32 prev_cur_ticks = cur_ticks; // to check for wrapping + InteractiveRandom(); // randomness + + while (this->PollEvent()) {} + + if (_exit_game) { + /* Restore saved resolution if in fullscreen mode. */ + if (this->IsFullscreen()) _cur_resolution = this->orig_res; + break; + } + + NSUInteger cur_mods = [ NSEvent modifierFlags ]; + +#if defined(_DEBUG) + if (cur_mods & NSShiftKeyMask) { +#else + if (_tab_is_down) { +#endif + if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; + } else if (_fast_forward & 2) { + _fast_forward = 0; + } + + cur_ticks = GetTick(); + if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) { + _realtime_tick += cur_ticks - last_cur_ticks; + last_cur_ticks = cur_ticks; + next_tick = cur_ticks + MILLISECONDS_PER_TICK; + + bool old_ctrl_pressed = _ctrl_pressed; + + _ctrl_pressed = (cur_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)) != 0; + _shift_pressed = (cur_mods & NSShiftKeyMask) != 0; + + if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); + + ::GameLoop(); + + UpdateWindows(); + this->CheckPaletteAnim(); + this->Draw(); + } else { +#ifdef _DEBUG + uint32 st0 = GetTick(); +#endif + CSleep(1); +#ifdef _DEBUG + st += GetTick() - st0; +#endif + NetworkDrawChatMessage(); + DrawMouseCursor(); + this->Draw(); + } + } + } + +#ifdef _DEBUG + uint32 et = GetTick(); + + DEBUG(driver, 1, "cocoa_v: nextEventMatchingMask took %i ms total", _tEvent); + DEBUG(driver, 1, "cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, et - et0 - st); + DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double)_tEvent / (double)(et - et0) * 100); + DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double)_tEvent / (double)(et - et0 - st) * 100); +#endif +} + + @implementation OTTD_QuartzView - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv diff --git a/src/video/cocoa/event.mm b/src/video/cocoa/event.mm deleted file mode 100644 index c7ab70538d..0000000000 --- a/src/video/cocoa/event.mm +++ /dev/null @@ -1,165 +0,0 @@ -/* - * This file is part of OpenTTD. - * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. - * OpenTTD 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 OpenTTD. If not, see . - */ - -/****************************************************************************** - * Cocoa video driver * - * Known things left to do: * - * Nothing at the moment. * - ******************************************************************************/ - -#ifdef WITH_COCOA - -#include "../../stdafx.h" - -#define Rect OTTDRect -#define Point OTTDPoint -#import -#undef Rect -#undef Point - -#include "../../openttd.h" -#include "../../debug.h" -#include "../../settings_type.h" -#include "../../core/geometry_type.hpp" -#include "cocoa_v.h" -#include "cocoa_wnd.h" -#include "../../blitter/factory.hpp" -#include "../../gfx_func.h" -#include "../../network/network.h" -#include "../../core/random_func.hpp" -#include "../../core/math_func.hpp" -#include "../../texteff.hpp" -#include "../../window_func.h" -#include "../../thread.h" - -#import /* gettimeofday */ - -/** - * Important notice regarding all modifications!!!!!!! - * There are certain limitations because the file is objective C++. - * gdb has limitations. - * C++ and objective C code can't be joined in all cases (classes stuff). - * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information. - */ - -extern bool _tab_is_down; - -#ifdef _DEBUG -static uint32 _tEvent; -#endif - - -static uint32 GetTick() -{ - struct timeval tim; - - gettimeofday(&tim, NULL); - return tim.tv_usec / 1000 + tim.tv_sec * 1000; -} - -bool VideoDriver_Cocoa::PollEvent() -{ -#ifdef _DEBUG - uint32 et0 = GetTick(); -#endif - NSEvent *event = [ NSApp nextEventMatchingMask:NSAnyEventMask - untilDate:[ NSDate distantPast ] - inMode:NSDefaultRunLoopMode dequeue:YES ]; -#ifdef _DEBUG - _tEvent += GetTick() - et0; -#endif - - if (event == nil) return false; - - [ NSApp sendEvent:event ]; - - return true; -} - - -void VideoDriver_Cocoa::GameLoop() -{ - uint32 cur_ticks = GetTick(); - uint32 last_cur_ticks = cur_ticks; - uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK; - -#ifdef _DEBUG - uint32 et0 = GetTick(); - uint32 st = 0; -#endif - - for (;;) { - @autoreleasepool { - - uint32 prev_cur_ticks = cur_ticks; // to check for wrapping - InteractiveRandom(); // randomness - - while (this->PollEvent()) {} - - if (_exit_game) { - /* Restore saved resolution if in fullscreen mode. */ - if (this->IsFullscreen()) _cur_resolution = this->orig_res; - break; - } - - NSUInteger cur_mods = [ NSEvent modifierFlags ]; - -#if defined(_DEBUG) - if (cur_mods & NSShiftKeyMask) -#else - if (_tab_is_down) -#endif - { - if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; - } else if (_fast_forward & 2) { - _fast_forward = 0; - } - - cur_ticks = GetTick(); - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) { - _realtime_tick += cur_ticks - last_cur_ticks; - last_cur_ticks = cur_ticks; - next_tick = cur_ticks + MILLISECONDS_PER_TICK; - - bool old_ctrl_pressed = _ctrl_pressed; - - _ctrl_pressed = !!(cur_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)); - _shift_pressed = !!(cur_mods & NSShiftKeyMask); - - if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); - - ::GameLoop(); - - UpdateWindows(); - this->CheckPaletteAnim(); - this->Draw(); - } else { -#ifdef _DEBUG - uint32 st0 = GetTick(); -#endif - CSleep(1); -#ifdef _DEBUG - st += GetTick() - st0; -#endif - NetworkDrawChatMessage(); - DrawMouseCursor(); - this->Draw(); - } - } - } - -#ifdef _DEBUG - uint32 et = GetTick(); - - DEBUG(driver, 1, "cocoa_v: nextEventMatchingMask took %i ms total", _tEvent); - DEBUG(driver, 1, "cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, et - et0 - st); - DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double)_tEvent / (double)(et - et0) * 100); - DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double)_tEvent / (double)(et - et0 - st) * 100); -#endif -} - -#endif /* WITH_COCOA */ From 2a8c3a2cf6460f8abb6032a449bf4cd4be01e847 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 6 Feb 2021 20:23:53 +0100 Subject: [PATCH 058/122] Codechange: [OSX] Align backing buffer pitch for a tiny bit performance. --- src/video/cocoa/cocoa_v.mm | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index c35ee9b7dd..f74c17c81e 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -109,6 +109,7 @@ VideoDriver_Cocoa::VideoDriver_Cocoa() { this->window_width = 0; this->window_height = 0; + this->window_pitch = 0; this->buffer_depth = 0; this->window_buffer = nullptr; this->pixel_buffer = nullptr; @@ -315,9 +316,9 @@ bool VideoDriver_Cocoa::IsFullscreen() void VideoDriver_Cocoa::GameSizeChanged() { /* Tell the game that the resolution has changed */ - _screen.width = this->window_width; - _screen.height = this->window_height; - _screen.pitch = this->window_width; + _screen.width = this->window_width; + _screen.height = this->window_height; + _screen.pitch = this->buffer_depth == 8 ? this->window_width : this->window_pitch; _screen.dst_ptr = this->buffer_depth == 8 ? this->pixel_buffer : this->window_buffer; /* Store old window size if we entered fullscreen mode. */ @@ -448,7 +449,7 @@ void VideoDriver_Cocoa::BlitIndexedToView32(int left, int top, int right, int bo const uint8 *src = (uint8*)this->pixel_buffer; uint32 *dst = (uint32*)this->window_buffer; uint width = this->window_width; - uint pitch = this->window_width; + uint pitch = this->window_pitch; for (int y = top; y < bottom; y++) { for (int x = left; x < right; x++) { @@ -538,13 +539,14 @@ void VideoDriver_Cocoa::AllocateBackingStore() this->window_width = (int)newframe.size.width; this->window_height = (int)newframe.size.height; + this->window_pitch = Align(this->window_width, 16 / sizeof(uint32)); // Quartz likes lines that are multiple of 16-byte. this->buffer_depth = BlitterFactory::GetCurrentBlitter()->GetScreenDepth(); /* Create Core Graphics Context */ free(this->window_buffer); - this->window_buffer = malloc(this->window_width * this->window_height * sizeof(uint32)); + this->window_buffer = malloc(this->window_pitch * this->window_height * sizeof(uint32)); /* Initialize with opaque black. */ - ClearWindowBuffer((uint32 *)this->window_buffer, this->window_width, this->window_height); + ClearWindowBuffer((uint32 *)this->window_buffer, this->window_pitch, this->window_height); CGContextRelease(this->cgcontext); this->cgcontext = CGBitmapContextCreate( @@ -552,7 +554,7 @@ void VideoDriver_Cocoa::AllocateBackingStore() this->window_width, // width this->window_height, // height 8, // bits per component - this->window_width * 4, // bytes per row + this->window_pitch * 4, // bytes per row this->color_space, // color space kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host ); @@ -566,6 +568,9 @@ void VideoDriver_Cocoa::AllocateBackingStore() free(this->pixel_buffer); this->pixel_buffer = malloc(this->window_width * this->window_height); if (this->pixel_buffer == nullptr) usererror("Out of memory allocating pixel buffer"); + } else { + free(this->pixel_buffer); + this->pixel_buffer = nullptr; } /* Redraw screen */ From 43326d11d8861721c9db45922bc7d42cf99dccba Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 6 Feb 2021 22:58:51 +0100 Subject: [PATCH 059/122] Change: [OSX] Use a layer-backed view to speed up drawing. --- CMakeLists.txt | 5 +++ src/video/cocoa/cocoa_v.mm | 86 +++++++------------------------------- 2 files changed, 19 insertions(+), 72 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5d7ac1137..2af3d30da3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,6 +131,7 @@ if(NOT WIN32) find_library(AUDIOTOOLBOX_LIBRARY AudioToolbox) find_library(AUDIOUNIT_LIBRARY AudioUnit) find_library(COCOA_LIBRARY Cocoa) + find_library(QUARTZCORE_LIBRARY QuartzCore) endif() endif() @@ -163,6 +164,9 @@ if(APPLE) if(NOT COCOA_LIBRARY) message(FATAL_ERROR "Cocoa is required for this platform") endif() + if(NOT QUARTZCORE_LIBRARY) + message(FATAL_ERROR "QuartzCore is required for this platform") + endif() endif() if(OPTION_PACKAGE_DEPENDENCIES) @@ -250,6 +254,7 @@ if(APPLE) ${AUDIOTOOLBOX_LIBRARY} ${AUDIOUNIT_LIBRARY} ${COCOA_LIBRARY} + ${QUARTZCORE_LIBRARY} ) add_definitions( diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index f74c17c81e..dea64e60df 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -100,8 +100,6 @@ static uint32 GetTick() VideoDriver_Cocoa *driver; } - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv; - -- (void)drawRect:(NSRect)invalidRect; @end @@ -708,6 +706,12 @@ void VideoDriver_Cocoa::GameLoop() { if (self = [ super initWithFrame:frameRect ]) { self->driver = drv; + + /* We manage our content updates ourselves. */ + self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay; + self.wantsLayer = YES; + + self.layer.magnificationFilter = kCAFilterNearest; } return self; } @@ -722,80 +726,18 @@ void VideoDriver_Cocoa::GameLoop() return YES; } -- (void)drawRect:(NSRect)invalidRect +- (BOOL)wantsUpdateLayer { - if (driver->cgcontext == nullptr) return; - - NSGraphicsContext *ctx = [ NSGraphicsContext currentContext ]; - CGContextRef viewContext = [ ctx respondsToSelector:@selector(CGContext) ] ? [ ctx CGContext ] : (CGContextRef)[ ctx graphicsPort ]; - CGContextSetShouldAntialias(viewContext, FALSE); - CGContextSetInterpolationQuality(viewContext, kCGInterpolationNone); + return YES; +} - /* The obtained 'rect' is actually a union of all dirty rects, let's ask for an explicit list of rects instead */ - const NSRect *dirtyRects; - NSInteger dirtyRectCount; - [ self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount ]; +- (void)updateLayer +{ + if (driver->cgcontext == nullptr) return; - /* We need an Image in order to do blitting, but as we don't touch the context between this call and drawing no copying will actually be done here */ + /* Set layer contents to our backing buffer, which avoids needless copying. */ CGImageRef fullImage = CGBitmapContextCreateImage(driver->cgcontext); - - /* Calculate total area we are blitting */ - uint32 blitArea = 0; - for (int n = 0; n < dirtyRectCount; n++) { - blitArea += (uint32)(dirtyRects[n].size.width * dirtyRects[n].size.height); - } - - /* - * This might be completely stupid, but in my extremely subjective opinion it feels faster - * The point is, if we're blitting less than 50% of the dirty rect union then it's still a good idea to blit each dirty - * rect separately but if we blit more than that, it's just cheaper to blit the entire union in one pass. - * Feel free to remove or find an even better value than 50% ... / blackis - */ - NSRect frameRect = [ self frame ]; - if (blitArea / (float)(invalidRect.size.width * invalidRect.size.height) > 0.5f) { - NSRect rect = invalidRect; - CGRect clipRect; - CGRect blitRect; - - blitRect.origin.x = rect.origin.x; - blitRect.origin.y = rect.origin.y; - blitRect.size.width = rect.size.width; - blitRect.size.height = rect.size.height; - - clipRect.origin.x = rect.origin.x; - clipRect.origin.y = frameRect.size.height - rect.origin.y - rect.size.height; - - clipRect.size.width = rect.size.width; - clipRect.size.height = rect.size.height; - - /* Blit dirty part of image */ - CGImageRef clippedImage = CGImageCreateWithImageInRect(fullImage, clipRect); - CGContextDrawImage(viewContext, blitRect, clippedImage); - CGImageRelease(clippedImage); - } else { - for (int n = 0; n < dirtyRectCount; n++) { - NSRect rect = dirtyRects[n]; - CGRect clipRect; - CGRect blitRect; - - blitRect.origin.x = rect.origin.x; - blitRect.origin.y = rect.origin.y; - blitRect.size.width = rect.size.width; - blitRect.size.height = rect.size.height; - - clipRect.origin.x = rect.origin.x; - clipRect.origin.y = frameRect.size.height - rect.origin.y - rect.size.height; - - clipRect.size.width = rect.size.width; - clipRect.size.height = rect.size.height; - - /* Blit dirty part of image */ - CGImageRef clippedImage = CGImageCreateWithImageInRect(fullImage, clipRect); - CGContextDrawImage(viewContext, blitRect, clippedImage); - CGImageRelease(clippedImage); - } - } - + self.layer.contents = (__bridge id)fullImage; CGImageRelease(fullImage); } From 649ff5f9f9777e0c8b66e868d24af37af6c942be Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sun, 7 Feb 2021 14:00:59 +0100 Subject: [PATCH 060/122] Codechange: [OSX] Use relative mouse handling during scrolling. --- src/video/cocoa/cocoa_wnd.mm | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index 2755b26ff4..8c947fe046 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -524,22 +524,25 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel - (void)internalMouseMoveEvent:(NSEvent *)event { - NSPoint pt = [ self mousePositionFromEvent:event ]; - - if (_cursor.UpdateCursorPosition(pt.x, pt.y, false) && [ NSApp isActive ]) { - /* Warping cursor when in foreground */ - NSPoint warp = [ self convertPoint:NSMakePoint(_cursor.pos.x, self.bounds.size.height - _cursor.pos.y) toView:nil ]; - warp = [ self.window convertRectToScreen:NSMakeRect(warp.x, warp.y, 0, 0) ].origin; - warp.y = NSScreen.screens[0].frame.size.height - warp.y; - - /* Do the actual warp */ - CGWarpMouseCursorPosition(NSPointToCGPoint(warp)); - /* this is the magic call that fixes cursor "freezing" after warp */ - CGAssociateMouseAndMouseCursorPosition(true); + if (_cursor.fix_at) { + _cursor.UpdateCursorPositionRelative(event.deltaX, event.deltaY); + } else { + NSPoint pt = [ self mousePositionFromEvent:event ]; + _cursor.UpdateCursorPosition(pt.x, pt.y, false); } + HandleMouseEvents(); } +- (void)internalMouseButtonEvent +{ + bool cur_fix = _cursor.fix_at; + HandleMouseEvents(); + + /* Cursor fix mode was changed, synchronize with OS. */ + if (cur_fix != _cursor.fix_at) CGAssociateMouseAndMouseCursorPosition(!_cursor.fix_at); +} + - (BOOL)emulateRightButton:(NSEvent *)event { uint32 keymask = 0; @@ -564,7 +567,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel [ self rightMouseDown:event ]; } else { _left_button_down = true; - [ self internalMouseMoveEvent:event ]; + [ self internalMouseButtonEvent ]; } } - (void)mouseUp:(NSEvent *)event @@ -574,7 +577,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel } else { _left_button_down = false; _left_button_clicked = false; - [ self internalMouseMoveEvent:event ]; + [ self internalMouseButtonEvent ]; } } @@ -586,12 +589,12 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel { _right_button_down = true; _right_button_clicked = true; - [ self internalMouseMoveEvent:event ]; + [ self internalMouseButtonEvent ]; } - (void)rightMouseUp:(NSEvent *)event { _right_button_down = false; - [ self internalMouseMoveEvent:event ]; + [ self internalMouseButtonEvent ]; } - (void)scrollWheel:(NSEvent *)event From 9c8721922b13a6789c9fed971f8550bda3099f7a Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sun, 7 Feb 2021 16:04:07 +0100 Subject: [PATCH 061/122] Fix: [OSX] An emulated right mouse down event has to be followed by right mouse up. --- src/video/cocoa/cocoa_wnd.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index 8c947fe046..5a3de7e553 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -451,6 +451,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel @implementation OTTD_CocoaView { float _current_magnification; NSUInteger _current_mods; + bool _emulated_down; } /** @@ -564,6 +565,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel - (void)mouseDown:(NSEvent *)event { if ([ self emulateRightButton:event ]) { + self->_emulated_down = true; [ self rightMouseDown:event ]; } else { _left_button_down = true; @@ -572,7 +574,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel } - (void)mouseUp:(NSEvent *)event { - if ([ self emulateRightButton:event ]) { + if (self->_emulated_down) { [ self rightMouseUp:event ]; } else { _left_button_down = false; From c78e559e88a7864a0b55cb00c82b6deac27ff1da Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Fri, 12 Feb 2021 01:18:36 +0100 Subject: [PATCH 062/122] Codechange: [OSX] Remove unused 'app active' flag. --- src/video/cocoa/cocoa_v.h | 3 +- src/video/cocoa/cocoa_v.mm | 1 - src/video/cocoa/cocoa_wnd.h | 3 -- src/video/cocoa/cocoa_wnd.mm | 55 ------------------------------------ 4 files changed, 1 insertion(+), 61 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index 8ebf5ba6e1..3f62102d14 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -39,8 +39,7 @@ private: uint32 palette[256]; ///< Colour Palette public: - bool active; ///< Whether the window is visible - bool setup; + bool setup; ///< Window is currently being created. OTTD_CocoaWindow *window; ///< Pointer to window object OTTD_CocoaView *cocoaview; ///< Pointer to view object diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index dea64e60df..40d041a5dd 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -111,7 +111,6 @@ VideoDriver_Cocoa::VideoDriver_Cocoa() this->buffer_depth = 0; this->window_buffer = nullptr; this->pixel_buffer = nullptr; - this->active = false; this->setup = false; this->window = nil; diff --git a/src/video/cocoa/cocoa_wnd.h b/src/video/cocoa/cocoa_wnd.h index 98ed7df7ec..fa7036396c 100644 --- a/src/video/cocoa/cocoa_wnd.h +++ b/src/video/cocoa/cocoa_wnd.h @@ -32,11 +32,8 @@ extern NSString *OTTDMainLaunchGameEngine; @interface OTTD_CocoaWindow : NSWindow - (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv; -- (void)miniaturize:(id)sender; - (void)display; - (void)setFrame:(NSRect)frameRect display:(BOOL)flag; -- (void)appDidHide:(NSNotification*)note; -- (void)appDidUnhide:(NSNotification*)note; @end /** Subclass of NSView to support mouse awareness and text input. */ diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index 5a3de7e553..a836646b56 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -370,13 +370,6 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel - (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv { if (self = [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]) { - /* Make our window subclass receive these application notifications */ - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ]; - - [ [ NSNotificationCenter defaultCenter ] addObserver:self - selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ]; - self->driver = drv; [ self setContentMinSize:NSMakeSize(64.0f, 64.0f) ]; @@ -391,17 +384,6 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel return self; } -/** - * Minimize the window - */ -- (void)miniaturize:(id)sender -{ - /* window is hidden now */ - driver->active = false; - - [ super miniaturize:sender ]; -} - /** * This method fires just before the window deminaturizes from the Dock. * We'll save the current visible surface, let the window manager redraw any @@ -418,9 +400,6 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel /* restore visible surface */ [ self restoreCachedImage ]; - - /* window is visible again */ - driver->active = true; } /** * Define the rectangle we draw our window in @@ -431,20 +410,6 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel driver->AllocateBackingStore(); } -/** - * Handle hiding of the application - */ -- (void)appDidHide:(NSNotification*)note -{ - driver->active = false; -} -/** - * Unhide and restore display plane and re-activate driver - */ -- (void)appDidUnhide:(NSNotification*)note -{ - driver->active = true; -} @end @@ -1122,26 +1087,6 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel return NO; } -/** Handle key acceptance */ -- (void)windowDidBecomeKey:(NSNotification*)aNotification -{ - driver->active = true; -} -/** Resign key acceptance */ -- (void)windowDidResignKey:(NSNotification*)aNotification -{ - driver->active = false; -} -/** Handle becoming main window */ -- (void)windowDidBecomeMain:(NSNotification*)aNotification -{ - driver->active = true; -} -/** Resign being main window */ -- (void)windowDidResignMain:(NSNotification*)aNotification -{ - driver->active = false; -} /** Window entered fullscreen mode (10.7). */ - (void)windowDidEnterFullScreen:(NSNotification *)aNotification { From acca56b6a5791e965950682ede1c97e2301564de Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Fri, 12 Feb 2021 01:19:00 +0100 Subject: [PATCH 063/122] Cleanup: [OSX] Improve some comments. --- src/video/cocoa/cocoa_v.h | 4 +--- src/video/cocoa/cocoa_v.mm | 45 +++++++++++++++++++----------------- src/video/cocoa/cocoa_wnd.mm | 4 ++-- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index 3f62102d14..596a9e27d4 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -63,12 +63,10 @@ public: void EditBoxLostFocus() override; - /** Return driver name */ const char *GetName() const override { return "cocoa"; } /* --- The following methods should be private, but can't be due to Obj-C limitations. --- */ - /** Main game loop. */ void GameLoop(); void AllocateBackingStore(); @@ -77,7 +75,7 @@ protected: Dimension GetScreenSize() const override; private: - bool PollEvent(); // In event.mm. + bool PollEvent(); bool IsFullscreen(); void GameSizeChanged(); diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 40d041a5dd..5493c06525 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -86,6 +86,10 @@ static const Dimension _default_resolutions[] = { static FVideoDriver_Cocoa iFVideoDriver_Cocoa; +/** + * Get current realtime. + * @return Tick time in milliseconds. + */ static uint32 GetTick() { struct timeval tim; @@ -95,7 +99,7 @@ static uint32 GetTick() } -/* Subclass of OTTD_CocoaView to fix Quartz rendering */ +/** Subclass of NSView for drawing to screen. */ @interface OTTD_QuartzView : NSView { VideoDriver_Cocoa *driver; } @@ -123,9 +127,7 @@ VideoDriver_Cocoa::VideoDriver_Cocoa() this->num_dirty_rects = lengthof(this->dirty_rects); } -/** - * Stop the cocoa video subdriver. - */ +/** Stop Cocoa video driver. */ void VideoDriver_Cocoa::Stop() { if (!_cocoa_video_started) return; @@ -146,9 +148,7 @@ void VideoDriver_Cocoa::Stop() _cocoa_video_started = false; } -/** - * Initialize a cocoa video subdriver. - */ +/** Try to start Cocoa video driver. */ const char *VideoDriver_Cocoa::Start(const StringList &parm) { if (!MacOSVersionIsAtLeast(10, 7, 0)) return "The Cocoa video driver requires Mac OS X 10.7 or later."; @@ -184,7 +184,6 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm) /** * Set dirty a rectangle managed by a cocoa video subdriver. - * * @param left Left x cooordinate of the dirty rectangle. * @param top Uppder y coordinate of the dirty rectangle. * @param width Width of the dirty rectangle. @@ -216,7 +215,6 @@ void VideoDriver_Cocoa::MainLoop() /** * Change the resolution when using a cocoa video driver. - * * @param w New window width. * @param h New window height. * @return Whether the video driver was successfully updated. @@ -250,7 +248,6 @@ bool VideoDriver_Cocoa::ChangeResolution(int w, int h) /** * Toggle between windowed and full screen mode for cocoa display driver. - * * @param full_screen Whether to switch to full screen or not. * @return Whether the mode switch was successful. */ @@ -269,7 +266,6 @@ bool VideoDriver_Cocoa::ToggleFullscreen(bool full_screen) /** * Callback invoked after the blitter was changed. - * * @return True if no error. */ bool VideoDriver_Cocoa::AfterBlitterChange() @@ -299,7 +295,7 @@ Dimension VideoDriver_Cocoa::GetScreenSize() const } /** - * Are we in fullscreen mode + * Are we in fullscreen mode? * @return whether fullscreen mode is currently used */ bool VideoDriver_Cocoa::IsFullscreen() @@ -329,7 +325,7 @@ void VideoDriver_Cocoa::GameSizeChanged() } /** - * Update the video modus. + * Update the video mode. */ void VideoDriver_Cocoa::UpdateVideoModes() { @@ -359,6 +355,7 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) { this->setup = true; + /* Limit window size to screen frame. */ NSSize screen_size = [ [ NSScreen mainScreen ] frame ].size; if (width > screen_size.width) width = screen_size.width; if (height > screen_size.height) height = screen_size.height; @@ -376,8 +373,7 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) /* Add built in full-screen support when available (OS X 10.7 and higher) * This code actually compiles for 10.5 and later, but only makes sense in conjunction - * with the quartz fullscreen support as found only in 10.7 and later - */ + * with the quartz fullscreen support as found only in 10.7 and later. */ if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) { NSWindowCollectionBehavior behavior = [ this->window collectionBehavior ]; behavior |= NSWindowCollectionBehaviorFullScreenPrimary; @@ -394,11 +390,11 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) [ this->window center ]; [ this->window makeKeyAndOrderFront:nil ]; - /* Create wrapper view for text input. */ + /* Create wrapper view for input and event handling. */ NSRect view_frame = [ this->window contentRectForFrameRect:[ this->window frame ] ]; this->cocoaview = [ [ OTTD_CocoaView alloc ] initWithFrame:view_frame ]; if (this->cocoaview == nil) { - DEBUG(driver, 0, "Could not create the text wrapper view."); + DEBUG(driver, 0, "Could not create the event wrapper view."); this->setup = false; return false; } @@ -413,6 +409,7 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) } [ draw_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ]; + /* Create view chain: window -> input wrapper view -> content view. */ [ this->window setContentView:this->cocoaview ]; [ this->cocoaview addSubview:draw_view ]; [ this->window makeFirstResponder:this->cocoaview ]; @@ -433,7 +430,7 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height) } /** - * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode. + * This function copies 8bpp pixels to the screen buffer in 32bpp windowed mode. * * @param left The x coord for the left edge of the box to blit. * @param top The y coord for the top edge of the box to blit. @@ -455,7 +452,8 @@ void VideoDriver_Cocoa::BlitIndexedToView32(int left, int top, int right, int bo } } -/** Draw window +/** + * Draw window. * @param force_update Whether to redraw unconditionally */ void VideoDriver_Cocoa::Draw(bool force_update) @@ -500,7 +498,7 @@ void VideoDriver_Cocoa::Draw(bool force_update) this->num_dirty_rects = 0; } -/** Update the palette */ +/** Update the palette. */ void VideoDriver_Cocoa::UpdatePalette(uint first_color, uint num_colors) { if (this->buffer_depth != 8) return; @@ -575,6 +573,7 @@ void VideoDriver_Cocoa::AllocateBackingStore() this->GameSizeChanged(); } +/** Check if palette updates need to be performed. */ void VideoDriver_Cocoa::CheckPaletteAnim() { if (_cur_palette.count_dirty != 0) { @@ -600,6 +599,10 @@ void VideoDriver_Cocoa::CheckPaletteAnim() } +/** + * Poll and handle a single event from the OS. + * @return True if there was an event to handle. + */ bool VideoDriver_Cocoa::PollEvent() { #ifdef _DEBUG @@ -617,7 +620,7 @@ bool VideoDriver_Cocoa::PollEvent() return true; } - +/** Main game loop. */ void VideoDriver_Cocoa::GameLoop() { uint32 cur_ticks = GetTick(); diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index a836646b56..089bfe8344 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -175,7 +175,7 @@ static std::vector NSStringToUTF32(NSString *s) { HandleExitGameRequest(); - return NSTerminateCancel; // NSTerminateLater ? + return NSTerminateCancel; } /** @@ -296,7 +296,7 @@ bool CocoaSetupApplication() } /** - * + * Deregister app delegate. */ void CocoaExitApplication() { From f1fc083f2b7f168931cd881cf040d7f3b084fb27 Mon Sep 17 00:00:00 2001 From: Charles Pigott Date: Sat, 13 Feb 2021 18:49:50 +0000 Subject: [PATCH 064/122] Change: Make order window hotkeys toggle for load & unload variants --- src/order_gui.cpp | 76 ++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 47 deletions(-) diff --git a/src/order_gui.cpp b/src/order_gui.cpp index d3b4aa9527..bd11656220 100644 --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -576,27 +576,22 @@ private: /** * Handle the click on the full load button. - * @param load_type the way to load. + * @param load_type Load flag to apply. If matches existing load type, toggles to default of 'load if possible'. + * @param toggle If we toggle or not (used for hotkey behavior) */ - void OrderClick_FullLoad(int load_type) + void OrderClick_FullLoad(OrderLoadFlags load_type, bool toggle = false) { VehicleOrderID sel_ord = this->OrderGetSel(); const Order *order = this->vehicle->GetOrder(sel_ord); - if (order == nullptr || order->GetLoadType() == load_type) return; + if (order == nullptr) return; - if (load_type < 0) { - load_type = order->GetLoadType() == OLF_LOAD_IF_POSSIBLE ? OLF_FULL_LOAD_ANY : OLF_LOAD_IF_POSSIBLE; + if (toggle && order->GetLoadType() == load_type) { + load_type = OLF_LOAD_IF_POSSIBLE; // reset to 'default' } - DoCommandP(this->vehicle->tile, this->vehicle->index + (sel_ord << 20), MOF_LOAD | (load_type << 4), CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER)); - } + if (order->GetLoadType() == load_type) return; // If we still match, do nothing - /** - * Handle the 'no loading' hotkey - */ - void OrderHotkey_NoLoad() - { - this->OrderClick_FullLoad(OLFB_NO_LOAD); + DoCommandP(this->vehicle->tile, this->vehicle->index + (sel_ord << 20), MOF_LOAD | (load_type << 4), CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER)); } /** @@ -631,17 +626,20 @@ private: /** * Handle the click on the unload button. + * @param unload_type Unload flag to apply. If matches existing unload type, toggles to default of 'unload if possible'. + * @param toggle If we toggle or not (used for hotkey behavior) */ - void OrderClick_Unload(int unload_type) + void OrderClick_Unload(OrderUnloadFlags unload_type, bool toggle = false) { VehicleOrderID sel_ord = this->OrderGetSel(); const Order *order = this->vehicle->GetOrder(sel_ord); - if (order == nullptr || order->GetUnloadType() == unload_type) return; + if (order == nullptr) return; - if (unload_type < 0) { - unload_type = order->GetUnloadType() == OUF_UNLOAD_IF_POSSIBLE ? OUFB_UNLOAD : OUF_UNLOAD_IF_POSSIBLE; + if (toggle && order->GetUnloadType() == unload_type) { + unload_type = OUF_UNLOAD_IF_POSSIBLE; } + if (order->GetUnloadType() == unload_type) return; // If we still match, do nothing DoCommandP(this->vehicle->tile, this->vehicle->index + (sel_ord << 20), MOF_UNLOAD | (unload_type << 4), CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER)); @@ -652,22 +650,6 @@ private: } } - /** - * Handle the transfer hotkey - */ - void OrderHotkey_Transfer() - { - this->OrderClick_Unload(OUFB_TRANSFER); - } - - /** - * Handle the 'no unload' hotkey - */ - void OrderHotkey_NoUnload() - { - this->OrderClick_Unload(OUFB_NO_UNLOAD); - } - /** * Handle the click on the nonstop button. * @param non_stop what non-stop type to use; -1 to use the 'next' one. @@ -1262,7 +1244,7 @@ public: case WID_O_FULL_LOAD: if (this->GetWidget(widget)->ButtonHit(pt)) { - this->OrderClick_FullLoad(-1); + this->OrderClick_FullLoad(OLF_FULL_LOAD_ANY, true); } else { ShowDropDownMenu(this, _order_full_load_drowdown, this->vehicle->GetOrder(this->OrderGetSel())->GetLoadType(), WID_O_FULL_LOAD, 0, 2); } @@ -1270,7 +1252,7 @@ public: case WID_O_UNLOAD: if (this->GetWidget(widget)->ButtonHit(pt)) { - this->OrderClick_Unload(-1); + this->OrderClick_Unload(OUFB_UNLOAD, true); } else { ShowDropDownMenu(this, _order_unload_drowdown, this->vehicle->GetOrder(this->OrderGetSel())->GetUnloadType(), WID_O_UNLOAD, 0, 8); } @@ -1361,11 +1343,11 @@ public: break; case WID_O_FULL_LOAD: - this->OrderClick_FullLoad(index); + this->OrderClick_FullLoad((OrderLoadFlags)index); break; case WID_O_UNLOAD: - this->OrderClick_Unload(index); + this->OrderClick_Unload((OrderUnloadFlags)index); break; case WID_O_GOTO: @@ -1434,17 +1416,17 @@ public: if (this->vehicle->owner != _local_company) return ES_NOT_HANDLED; switch (hotkey) { - case OHK_SKIP: this->OrderClick_Skip(); break; - case OHK_DELETE: this->OrderClick_Delete(); break; + case OHK_SKIP: this->OrderClick_Skip(); break; + case OHK_DELETE: this->OrderClick_Delete(); break; case OHK_GOTO: this->OrderClick_Goto(OPOS_GOTO); break; - case OHK_NONSTOP: this->OrderClick_Nonstop(-1); break; - case OHK_FULLLOAD: this->OrderClick_FullLoad(-1); break; - case OHK_UNLOAD: this->OrderClick_Unload(-1); break; - case OHK_NEAREST_DEPOT: this->OrderClick_NearestDepot(); break; - case OHK_ALWAYS_SERVICE: this->OrderClick_Service(-1); break; - case OHK_TRANSFER: this->OrderHotkey_Transfer(); break; - case OHK_NO_UNLOAD: this->OrderHotkey_NoUnload(); break; - case OHK_NO_LOAD: this->OrderHotkey_NoLoad(); break; + case OHK_NONSTOP: this->OrderClick_Nonstop(-1); break; + case OHK_FULLLOAD: this->OrderClick_FullLoad(OLF_FULL_LOAD_ANY, true); break; + case OHK_UNLOAD: this->OrderClick_Unload(OUFB_UNLOAD, true); break; + case OHK_NEAREST_DEPOT: this->OrderClick_NearestDepot(); break; + case OHK_ALWAYS_SERVICE: this->OrderClick_Service(-1); break; + case OHK_TRANSFER: this->OrderClick_Unload(OUFB_TRANSFER, true); break; + case OHK_NO_UNLOAD: this->OrderClick_Unload(OUFB_NO_UNLOAD, true); break; + case OHK_NO_LOAD: this->OrderClick_FullLoad(OLFB_NO_LOAD, true); break; default: return ES_NOT_HANDLED; } return ES_HANDLED; From 2b08f21625da0c0425bc8f5843b802f84b72a0b6 Mon Sep 17 00:00:00 2001 From: Charles Pigott Date: Sat, 13 Feb 2021 21:50:51 +0000 Subject: [PATCH 065/122] Fix #8625: Wrong ending year was displayed in highscore table --- known-bugs.txt | 9 +++++++++ src/highscore_gui.cpp | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/known-bugs.txt b/known-bugs.txt index a687dff4a5..ddfe7aa0c1 100644 --- a/known-bugs.txt +++ b/known-bugs.txt @@ -409,3 +409,12 @@ Involuntary cargo exchange with cargodist via neutral station [#6114]: shared station make the order "no unload" and if you're unloading make it "no load". Cargodist will then figure out that it should not create such a route. + +Incorrect ending year displayed in end of game newspaper [#8625] + The ending year of the game is configurable, but the date displayed in + the newspaper at the end of the game is part of the graphics, not text. + So to fix this would involve fixing the graphics in every baseset, + including the original. Additionally, basesets are free to put this + text in different positions (which they do), making a proper solution + to this infinitely more complex for a part of the game that fewer than + 1% of players ever see. diff --git a/src/highscore_gui.cpp b/src/highscore_gui.cpp index a473424f81..b1c428a768 100644 --- a/src/highscore_gui.cpp +++ b/src/highscore_gui.cpp @@ -183,7 +183,7 @@ struct HighScoreWindow : EndGameHighScoreBaseWindow { this->SetupHighScoreEndWindow(); Point pt = this->GetTopLeft(ScaleGUITrad(640), ScaleGUITrad(480)); - SetDParam(0, ORIGINAL_END_YEAR); + SetDParam(0, _settings_game.game_creation.ending_year); DrawStringMultiLine(pt.x + ScaleGUITrad(70), pt.x + ScaleGUITrad(570), pt.y, pt.y + ScaleGUITrad(140), !_networking ? STR_HIGHSCORE_TOP_COMPANIES_WHO_REACHED : STR_HIGHSCORE_TOP_COMPANIES_NETWORK_GAME, TC_FROMSTRING, SA_CENTER); /* Draw Highscore peepz */ From 8121706b89998769fc37c02e89205ca414358f82 Mon Sep 17 00:00:00 2001 From: Charles Pigott Date: Sat, 13 Feb 2021 21:18:03 +0000 Subject: [PATCH 066/122] Fix #8620: Scale spacing between date & news in history window according to font scaling --- src/news_gui.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/news_gui.cpp b/src/news_gui.cpp index 639c075746..3412c351ce 100644 --- a/src/news_gui.cpp +++ b/src/news_gui.cpp @@ -34,6 +34,7 @@ #include "settings_internal.h" #include "guitimer_func.h" #include "group_gui.h" +#include "zoom_func.h" #include "widgets/news_widget.h" @@ -1174,8 +1175,8 @@ struct MessageHistoryWindow : Window { bool rtl = _current_text_dir == TD_RTL; uint date_left = rtl ? r.right - WD_FRAMERECT_RIGHT - this->date_width : r.left + WD_FRAMERECT_LEFT; uint date_right = rtl ? r.right - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT + this->date_width; - uint news_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.left + WD_FRAMERECT_LEFT + this->date_width + WD_FRAMERECT_RIGHT; - uint news_right = rtl ? r.right - WD_FRAMERECT_RIGHT - this->date_width - WD_FRAMERECT_RIGHT : r.right - WD_FRAMERECT_RIGHT; + uint news_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.left + WD_FRAMERECT_LEFT + this->date_width + WD_FRAMERECT_RIGHT + ScaleFontTrad(5); + uint news_right = rtl ? r.right - WD_FRAMERECT_RIGHT - this->date_width - WD_FRAMERECT_RIGHT - ScaleFontTrad(5) : r.right - WD_FRAMERECT_RIGHT; for (int n = this->vscroll->GetCapacity(); n > 0; n--) { SetDParam(0, ni->date); DrawString(date_left, date_right, y, STR_SHORT_DATE); From 21a2cd7bc386207c68735edc05a21bb987dddd6b Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 13 Feb 2021 22:44:45 +0100 Subject: [PATCH 067/122] Codechange: Replace magic numbers by constants. --- src/fontcache.cpp | 4 ++-- src/fontcache_internal.h | 3 +++ src/os/windows/font_win32.cpp | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 6a0b500d86..50d2cf22e4 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -440,7 +440,7 @@ void FreeTypeFontCache::SetFontSize(FontSize fs, FT_Face face, int pixels) /* Font height is minimum height plus the difference between the default * height for this font size and the small size. */ int diff = scaled_height - ScaleFontTrad(_default_font_height[FS_SMALL]); - pixels = Clamp(std::min(head->Lowest_Rec_PPEM, 20u) + diff, scaled_height, MAX_FONT_SIZE); + pixels = Clamp(std::min(head->Lowest_Rec_PPEM, MAX_FONT_MIN_REC_SIZE) + diff, scaled_height, MAX_FONT_SIZE); } } else { pixels = ScaleFontTrad(pixels); @@ -608,7 +608,7 @@ const Sprite *FreeTypeFontCache::InternalGetGlyph(GlyphID key, bool aa) uint height = std::max(1U, (uint)slot->bitmap.rows + (this->fs == FS_NORMAL)); /* Limit glyph size to prevent overflows later on. */ - if (width > 256 || height > 256) usererror("Font glyph is too large"); + if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) usererror("Font glyph is too large"); /* FreeType has rendered the glyph, now we allocate a sprite and copy the image into it */ SpriteLoader::Sprite sprite; diff --git a/src/fontcache_internal.h b/src/fontcache_internal.h index 09e676234f..2f74547338 100644 --- a/src/fontcache_internal.h +++ b/src/fontcache_internal.h @@ -22,6 +22,9 @@ static const byte SHADOW_COLOUR = 2; /** Font cache for fonts that are based on a TrueType font. */ class TrueTypeFontCache : public FontCache { protected: + static constexpr int MAX_GLYPH_DIM = 256; ///< Maximum glyph dimensions. + static constexpr uint MAX_FONT_MIN_REC_SIZE = 20u; ///< Upper limit for the recommended font size in case a font file contains nonsensical values. + int req_size; ///< Requested font size. int used_size; ///< Used font size. diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index 4bcbb145d9..7df24828b7 100644 --- a/src/os/windows/font_win32.cpp +++ b/src/os/windows/font_win32.cpp @@ -414,7 +414,7 @@ void Win32FontCache::SetFontSize(FontSize fs, int pixels) /* Font height is minimum height plus the difference between the default * height for this font size and the small size. */ int diff = scaled_height - ScaleFontTrad(this->GetDefaultFontHeight(FS_SMALL)); - pixels = Clamp(std::min(otm->otmusMinimumPPEM, 20u) + diff, scaled_height, MAX_FONT_SIZE); + pixels = Clamp(std::min(otm->otmusMinimumPPEM, MAX_FONT_MIN_REC_SIZE) + diff, scaled_height, MAX_FONT_SIZE); SelectObject(dc, old); DeleteObject(temp); @@ -489,7 +489,7 @@ void Win32FontCache::ClearFontCache() uint height = std::max(1U, (uint)gm.gmBlackBoxY + (this->fs == FS_NORMAL)); /* Limit glyph size to prevent overflows later on. */ - if (width > 256 || height > 256) usererror("Font glyph is too large"); + if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) usererror("Font glyph is too large"); /* GDI has rendered the glyph, now we allocate a sprite and copy the image into it. */ SpriteLoader::Sprite sprite; From 6755ff63e168dca62d16f3797a1a9dd5ceba307a Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 13 Feb 2021 22:51:18 +0100 Subject: [PATCH 068/122] Add: [OSX] Native font rendering without using FreeType. --- .github/workflows/ci-build.yml | 2 +- .github/workflows/release.yml | 2 +- CMakeLists.txt | 2 +- src/bootstrap_gui.cpp | 4 +- src/fontcache.cpp | 7 +- src/os/macosx/CMakeLists.txt | 1 + src/os/macosx/font_osx.cpp | 279 +++++++++++++++++++++++++++++++++ src/os/macosx/font_osx.h | 40 +++++ src/os/macosx/string_osx.cpp | 16 +- src/settings.cpp | 4 +- src/strings.cpp | 4 +- src/textfile_gui.cpp | 2 +- 12 files changed, 345 insertions(+), 18 deletions(-) create mode 100644 src/os/macosx/font_osx.h diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 029c0416e9..0d93ce944b 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -161,7 +161,7 @@ jobs: vcpkgDirectory: '/usr/local/share/vcpkg' doNotUpdateVcpkg: false vcpkgGitCommitId: 2a42024b53ebb512fb5dd63c523338bf26c8489c - vcpkgArguments: 'freetype liblzma lzo' + vcpkgArguments: 'liblzma lzo' vcpkgTriplet: '${{ matrix.arch }}-osx' - name: Install OpenGFX diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07feba9186..d31df95ca7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -482,7 +482,7 @@ jobs: vcpkgDirectory: '/usr/local/share/vcpkg' doNotUpdateVcpkg: false vcpkgGitCommitId: 2a42024b53ebb512fb5dd63c523338bf26c8489c - vcpkgArguments: 'freetype:x64-osx liblzma:x64-osx lzo:x64-osx freetype:arm64-osx liblzma:arm64-osx lzo:arm64-osx' + vcpkgArguments: 'liblzma:x64-osx lzo:x64-osx liblzma:arm64-osx lzo:arm64-osx' - name: Build tools run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 2af3d30da3..24496f459c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,8 +116,8 @@ find_package(PNG) if(NOT WIN32) find_package(Allegro) - find_package(Freetype) if(NOT APPLE) + find_package(Freetype) find_package(SDL2) if(NOT SDL2_FOUND) find_package(SDL) diff --git a/src/bootstrap_gui.cpp b/src/bootstrap_gui.cpp index a0f040d1aa..7ec391b47d 100644 --- a/src/bootstrap_gui.cpp +++ b/src/bootstrap_gui.cpp @@ -11,7 +11,7 @@ #include "base_media_base.h" #include "blitter/factory.hpp" -#if defined(WITH_FREETYPE) || defined(WITH_UNISCRIBE) +#if defined(WITH_FREETYPE) || defined(WITH_UNISCRIBE) || defined(WITH_COCOA) #include "core/geometry_func.hpp" #include "fontcache.h" @@ -218,7 +218,7 @@ bool HandleBootstrap() if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) goto failure; /* If there is no network or no freetype, then there is nothing we can do. Go straight to failure. */ -#if (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) +#if (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) || defined(WITH_COCOA) if (!_network_available) goto failure; /* First tell the game we're bootstrapping. */ diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 50d2cf22e4..be36ac1076 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -697,6 +697,9 @@ void InitFreeType(bool monospace) #elif defined(_WIN32) extern void LoadWin32Font(FontSize fs); LoadWin32Font(fs); +#elif defined(WITH_COCOA) + extern void LoadCoreTextFont(FontSize fs); + LoadCoreTextFont(fs); #endif } } @@ -717,11 +720,11 @@ void UninitFreeType() #endif /* WITH_FREETYPE */ } -#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) +#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) #ifdef WITH_FREETYPE FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) { return FT_Err_Cannot_Open_Resource; } #endif /* WITH_FREETYPE */ bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback) { return false; } -#endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) */ +#endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) */ diff --git a/src/os/macosx/CMakeLists.txt b/src/os/macosx/CMakeLists.txt index 6b4f2f279d..645a057c7d 100644 --- a/src/os/macosx/CMakeLists.txt +++ b/src/os/macosx/CMakeLists.txt @@ -1,6 +1,7 @@ add_files( crashlog_osx.cpp font_osx.cpp + font_osx.h macos.h macos.mm osx_stdafx.h diff --git a/src/os/macosx/font_osx.cpp b/src/os/macosx/font_osx.cpp index f9436312b5..4ce2b24e09 100644 --- a/src/os/macosx/font_osx.cpp +++ b/src/os/macosx/font_osx.cpp @@ -9,13 +9,21 @@ #include "../../stdafx.h" #include "../../debug.h" +#include "font_osx.h" +#include "../../blitter/factory.hpp" +#include "../../fileio_func.h" #include "../../fontdetection.h" #include "../../string_func.h" #include "../../strings_func.h" +#include "../../zoom_func.h" #include "macos.h" +#include + +#include "../../table/control_codes.h" #include "safeguards.h" + #ifdef WITH_FREETYPE #include @@ -136,3 +144,274 @@ bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, i callback->FindMissingGlyphs(); return result; } + + +CoreTextFontCache::CoreTextFontCache(FontSize fs, CFAutoRelease &&font, int pixels) : TrueTypeFontCache(fs, pixels), font_desc(std::move(font)) +{ + this->SetFontSize(pixels); +} + +/** + * Reset cached glyphs. + */ +void CoreTextFontCache::ClearFontCache() +{ + /* GUI scaling might have changed, determine font size anew if it was automatically selected. */ + if (this->font) this->SetFontSize(this->req_size); + + this->TrueTypeFontCache::ClearFontCache(); +} + +void CoreTextFontCache::SetFontSize(int pixels) +{ + if (pixels == 0) { + /* Try to determine a good height based on the height recommended by the font. */ + int scaled_height = ScaleFontTrad(this->GetDefaultFontHeight(this->fs)); + pixels = scaled_height; + + CFAutoRelease font(CTFontCreateWithFontDescriptor(this->font_desc.get(), 0.0f, nullptr)); + if (font) { + float min_size = 0.0f; + + /* The 'head' TrueType table contains information about the + * 'smallest readable size in pixels'. Try to read it, if + * that doesn't work, we use the default OS font size instead. + * + * Reference: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6head.html */ + CFAutoRelease data(CTFontCopyTable(font.get(), kCTFontTableHead, kCTFontTableOptionNoOptions)); + if (data) { + uint16_t lowestRecPPEM; // At offset 46 of the 'head' TrueType table. + CFDataGetBytes(data.get(), CFRangeMake(46, sizeof(lowestRecPPEM)), (UInt8 *)&lowestRecPPEM); + min_size = CFSwapInt16BigToHost(lowestRecPPEM); // TrueType data is always big-endian. + } else { + CFAutoRelease size((CFNumberRef)CTFontCopyAttribute(font.get(), kCTFontSizeAttribute)); + CFNumberGetValue(size.get(), kCFNumberFloatType, &min_size); + } + + /* Font height is minimum height plus the difference between the default + * height for this font size and the small size. */ + int diff = scaled_height - ScaleFontTrad(this->GetDefaultFontHeight(FS_SMALL)); + pixels = Clamp(std::min(min_size, MAX_FONT_MIN_REC_SIZE) + diff, scaled_height, MAX_FONT_SIZE); + } + } else { + pixels = ScaleFontTrad(pixels); + } + this->used_size = pixels; + + this->font.reset(CTFontCreateWithFontDescriptor(this->font_desc.get(), pixels, nullptr)); + + /* Query the font metrics we needed. We generally round all values up to + * make sure we don't inadvertently cut off a row or column of pixels, + * except when determining glyph to glyph advances. */ + this->units_per_em = CTFontGetUnitsPerEm(this->font.get()); + this->ascender = (int)std::ceil(CTFontGetAscent(this->font.get())); + this->descender = -(int)std::ceil(CTFontGetDescent(this->font.get())); + this->height = this->ascender - this->descender; + + /* Get real font name. */ + char name[128]; + CFAutoRelease font_name((CFStringRef)CTFontCopyAttribute(this->font.get(), kCTFontDisplayNameAttribute)); + CFStringGetCString(font_name.get(), name, lengthof(name), kCFStringEncodingUTF8); + this->font_name = name; + + DEBUG(freetype, 2, "Loaded font '%s' with size %d", this->font_name.c_str(), pixels); +} + +GlyphID CoreTextFontCache::MapCharToGlyph(WChar key) +{ + assert(IsPrintable(key)); + + if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { + return this->parent->MapCharToGlyph(key); + } + + /* Convert characters outside of the Basic Multilingual Plane into surrogate pairs. */ + UniChar chars[2]; + if (key >= 0x010000U) { + chars[0] = (UniChar)(((key - 0x010000U) >> 10) + 0xD800); + chars[1] = (UniChar)(((key - 0x010000U) & 0x3FF) + 0xDC00); + } else { + chars[0] = (UniChar)(key & 0xFFFF); + } + + CGGlyph glyph[2] = {0, 0}; + if (CTFontGetGlyphsForCharacters(this->font.get(), chars, glyph, key >= 0x010000U ? 2 : 1)) { + return glyph[0]; + } + + return 0; +} + +const void *CoreTextFontCache::InternalGetFontTable(uint32 tag, size_t &length) +{ + CFAutoRelease data(CTFontCopyTable(this->font.get(), (CTFontTableTag)tag, kCTFontTableOptionNoOptions)); + if (!data) return nullptr; + + length = CFDataGetLength(data.get()); + auto buf = MallocT(length); + + CFDataGetBytes(data.get(), CFRangeMake(0, (CFIndex)length), buf); + return buf; +} + +const Sprite *CoreTextFontCache::InternalGetGlyph(GlyphID key, bool use_aa) +{ + /* Get glyph size. */ + CGGlyph glyph = (CGGlyph)key; + CGRect bounds = CGRectNull; + if (MacOSVersionIsAtLeast(10, 8, 0)) { + bounds = CTFontGetOpticalBoundsForGlyphs(this->font.get(), &glyph, nullptr, 1, 0); + } else { + bounds = CTFontGetBoundingRectsForGlyphs(this->font.get(), kCTFontOrientationDefault, &glyph, nullptr, 1); + } + if (CGRectIsNull(bounds)) usererror("Unable to render font glyph"); + + uint bb_width = (uint)std::ceil(bounds.size.width) + 1; // Sometimes the glyph bounds are too tight and cut of the last pixel after rounding. + uint bb_height = (uint)std::ceil(bounds.size.height); + + /* Add 1 pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel. */ + uint width = std::max(1U, bb_width + (this->fs == FS_NORMAL ? 1 : 0)); + uint height = std::max(1U, bb_height + (this->fs == FS_NORMAL ? 1 : 0)); + + /* Limit glyph size to prevent overflows later on. */ + if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) usererror("Font glyph is too large"); + + SpriteLoader::Sprite sprite; + sprite.AllocateData(ZOOM_LVL_NORMAL, width * height); + sprite.type = ST_FONT; + sprite.width = width; + sprite.height = height; + sprite.x_offs = (int16)std::round(CGRectGetMinX(bounds)); + sprite.y_offs = this->ascender - (int16)std::ceil(CGRectGetMaxY(bounds)); + + if (bounds.size.width > 0) { + /* Glyph is not a white-space glyph. Render it to a bitmap context. */ + + /* We only need the alpha channel, as we apply our own colour constants to the sprite. */ + int pitch = Align(bb_width, 16); + byte *bmp = CallocT(bb_height * pitch); + CFAutoRelease context(CGBitmapContextCreate(bmp, bb_width, bb_height, 8, pitch, nullptr, kCGImageAlphaOnly)); + /* Set antialias according to requirements. */ + CGContextSetAllowsAntialiasing(context.get(), use_aa); + CGContextSetAllowsFontSubpixelPositioning(context.get(), use_aa); + CGContextSetAllowsFontSubpixelQuantization(context.get(), !use_aa); + CGContextSetShouldSmoothFonts(context.get(), false); + + float offset = 0.5f; // CoreText uses 0.5 as pixel centers. We want pixel alignment. + CGPoint pos{offset - bounds.origin.x, offset - bounds.origin.y}; + CTFontDrawGlyphs(this->font.get(), &glyph, &pos, 1, context.get()); + + /* Draw shadow for medium size. */ + if (this->fs == FS_NORMAL && !use_aa) { + for (uint y = 0; y < bb_height; y++) { + for (uint x = 0; x < bb_width; x++) { + if (bmp[y * pitch + x] > 0) { + sprite.data[1 + x + (1 + y) * sprite.width].m = SHADOW_COLOUR; + sprite.data[1 + x + (1 + y) * sprite.width].a = use_aa ? bmp[x + y * pitch] : 0xFF; + } + } + } + } + + /* Extract pixel data. */ + for (uint y = 0; y < bb_height; y++) { + for (uint x = 0; x < bb_width; x++) { + if (bmp[y * pitch + x] > 0) { + sprite.data[x + y * sprite.width].m = FACE_COLOUR; + sprite.data[x + y * sprite.width].a = use_aa ? bmp[x + y * pitch] : 0xFF; + } + } + } + } + + GlyphEntry new_glyph; + new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(&sprite, AllocateFont); + new_glyph.width = (byte)std::round(CTFontGetAdvancesForGlyphs(this->font.get(), kCTFontOrientationDefault, &glyph, nullptr, 1)); + this->SetGlyphPtr(key, &new_glyph); + + return new_glyph.sprite; +} + +/** + * Loads the TrueType font. + * If a CoreText font description is present, e.g. from the automatic font + * fallback search, use it. Otherwise, try to resolve it by font name. + * @param fs The font size to load. + */ +void LoadCoreTextFont(FontSize fs) +{ + static const char *SIZE_TO_NAME[] = { "medium", "small", "large", "mono" }; + + FreeTypeSubSetting *settings = nullptr; + switch (fs) { + default: NOT_REACHED(); + case FS_SMALL: settings = &_freetype.small; break; + case FS_NORMAL: settings = &_freetype.medium; break; + case FS_LARGE: settings = &_freetype.large; break; + case FS_MONO: settings = &_freetype.mono; break; + } + + if (StrEmpty(settings->font)) return; + + CFAutoRelease font_ref; + + if (settings->os_handle != nullptr) { + font_ref.reset(static_cast(const_cast(settings->os_handle))); + CFRetain(font_ref.get()); // Increase ref count to match a later release. + } + + if (!font_ref && MacOSVersionIsAtLeast(10, 6, 0)) { + /* Might be a font file name, try load it. Direct font loading is + * only supported starting on OSX 10.6. */ + CFAutoRelease path; + + /* See if this is an absolute path. */ + if (FileExists(settings->font)) { + path.reset(CFStringCreateWithCString(kCFAllocatorDefault, settings->font, kCFStringEncodingUTF8)); + } else { + /* Scan the search-paths to see if it can be found. */ + std::string full_font = FioFindFullPath(BASE_DIR, settings->font); + if (!full_font.empty()) { + path.reset(CFStringCreateWithCString(kCFAllocatorDefault, full_font.c_str(), kCFStringEncodingUTF8)); + } + } + + if (path) { + /* Try getting a font descriptor to see if the system can use it. */ + CFAutoRelease url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle, false)); + CFAutoRelease descs(CTFontManagerCreateFontDescriptorsFromURL(url.get())); + + if (descs && CFArrayGetCount(descs.get()) > 0) { + font_ref.reset((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0)); + CFRetain(font_ref.get()); + } else { + ShowInfoF("Unable to load file '%s' for %s font, using default OS font selection instead", settings->font, SIZE_TO_NAME[fs]); + } + } + } + + if (!font_ref) { + CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, settings->font, kCFStringEncodingUTF8)); + + /* Simply creating the font using CTFontCreateWithNameAndSize will *always* return + * something, no matter the name. As such, we can't use it to check for existence. + * We instead query the list of all font descriptors that match the given name which + * does not do this stupid name fallback. */ + CFAutoRelease name_desc(CTFontDescriptorCreateWithNameAndSize(name.get(), 0.0)); + CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontNameAttribute)), 1, &kCFTypeSetCallBacks)); + CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(name_desc.get(), mandatory_attribs.get())); + + /* Assume the first result is the one we want. */ + if (descs && CFArrayGetCount(descs.get()) > 0) { + font_ref.reset((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0)); + CFRetain(font_ref.get()); + } + } + + if (!font_ref) { + ShowInfoF("Unable to use '%s' for %s font, using sprite font instead", settings->font, SIZE_TO_NAME[fs]); + return; + } + + new CoreTextFontCache(fs, std::move(font_ref), settings->size); +} diff --git a/src/os/macosx/font_osx.h b/src/os/macosx/font_osx.h new file mode 100644 index 0000000000..bdfd7316d0 --- /dev/null +++ b/src/os/macosx/font_osx.h @@ -0,0 +1,40 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** @file font_osx.h Functions related to font handling on MacOS. */ + +#ifndef FONT_OSX_H +#define FONT_OSX_H + +#include "../../fontcache_internal.h" +#include "os/macosx/macos.h" + +#include + +class CoreTextFontCache : public TrueTypeFontCache { + CFAutoRelease font_desc; ///< Font descriptor exlcuding font size. + CFAutoRelease font; ///< CoreText font handle. + + std::string font_name; ///< Cached font name. + + void SetFontSize(int pixels); + const Sprite *InternalGetGlyph(GlyphID key, bool use_aa) override; + const void *InternalGetFontTable(uint32 tag, size_t &length) override; +public: + CoreTextFontCache(FontSize fs, CFAutoRelease &&font, int pixels); + ~CoreTextFontCache() {} + + void ClearFontCache() override; + GlyphID MapCharToGlyph(WChar key) override; + const char *GetFontName() override { return font_name.c_str(); } + bool IsBuiltInFont() override { return false; } + const void *GetOSHandle() override { return font.get(); } +}; + +void LoadCoreTextFont(FontSize fs); + +#endif /* FONT_OSX_H */ diff --git a/src/os/macosx/string_osx.cpp b/src/os/macosx/string_osx.cpp index 12440e1e1c..5cd14e8e1e 100644 --- a/src/os/macosx/string_osx.cpp +++ b/src/os/macosx/string_osx.cpp @@ -174,12 +174,16 @@ static CTRunDelegateCallbacks _sprite_font_callback = { for (const auto &i : fontMapping) { if (i.first - last == 0) continue; - if (!_font_cache[i.second->fc->GetSize()]) { - /* Cache font information. */ - CFAutoRelease font_name(CFStringCreateWithCString(kCFAllocatorDefault, i.second->fc->GetFontName(), kCFStringEncodingUTF8)); - _font_cache[i.second->fc->GetSize()].reset(CTFontCreateWithName(font_name.get(), i.second->fc->GetFontSize(), nullptr)); + CTFontRef font = (CTFontRef)i.second->fc->GetOSHandle(); + if (font == nullptr) { + if (!_font_cache[i.second->fc->GetSize()]) { + /* Cache font information. */ + CFAutoRelease font_name(CFStringCreateWithCString(kCFAllocatorDefault, i.second->fc->GetFontName(), kCFStringEncodingUTF8)); + _font_cache[i.second->fc->GetSize()].reset(CTFontCreateWithName(font_name.get(), i.second->fc->GetFontSize(), nullptr)); + } + font = _font_cache[i.second->fc->GetSize()].get(); } - CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, i.first - last), kCTFontAttributeName, _font_cache[i.second->fc->GetSize()].get()); + CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, i.first - last), kCTFontAttributeName, font); CGColorRef color = CGColorCreateGenericGray((uint8)i.second->colour / 255.0f, 1.0f); // We don't care about the real colours, just that they are different. CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, i.first - last), kCTForegroundColorAttributeName, color); @@ -300,7 +304,7 @@ void MacOSRegisterExternalFont(const char *file_path) CTFontManagerRegisterFontsForURL(url.get(), kCTFontManagerScopeProcess, nullptr); } -/** Store current language locale as a CoreFounation locale. */ +/** Store current language locale as a CoreFoundation locale. */ void MacOSSetCurrentLocaleName(const char *iso_code) { if (!MacOSVersionIsAtLeast(10, 5, 0)) return; diff --git a/src/settings.cpp b/src/settings.cpp index b5a07fbb25..d3be596929 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -38,7 +38,7 @@ #include "sound_func.h" #include "company_func.h" #include "rev.h" -#if defined(WITH_FREETYPE) || defined(_WIN32) +#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) #include "fontcache.h" #endif #include "textbuf_gui.h" @@ -67,7 +67,7 @@ #include "void_map.h" #include "station_base.h" -#if defined(WITH_FREETYPE) || defined(_WIN32) +#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) #define HAS_TRUETYPE_FONT #endif diff --git a/src/strings.cpp b/src/strings.cpp index 1d6fca8b6f..acebf88eed 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2073,7 +2073,7 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher { void SetFontNames(FreeTypeSettings *settings, const char *font_name, const void *os_data) override { -#if defined(WITH_FREETYPE) || defined(_WIN32) +#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) strecpy(settings->small.font, font_name, lastof(settings->small.font)); strecpy(settings->medium.font, font_name, lastof(settings->medium.font)); strecpy(settings->large.font, font_name, lastof(settings->large.font)); @@ -2103,7 +2103,7 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher) static LanguagePackGlyphSearcher pack_searcher; if (searcher == nullptr) searcher = &pack_searcher; bool bad_font = !base_font || searcher->FindMissingGlyphs(); -#if defined(WITH_FREETYPE) || defined(_WIN32) +#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) if (bad_font) { /* We found an unprintable character... lets try whether we can find * a fallback font that can print the characters in the current language. */ diff --git a/src/textfile_gui.cpp b/src/textfile_gui.cpp index fd544f5fc4..075898fd71 100644 --- a/src/textfile_gui.cpp +++ b/src/textfile_gui.cpp @@ -194,7 +194,7 @@ void TextfileWindow::SetupScrollbars() /* virtual */ void TextfileWindow::SetFontNames(FreeTypeSettings *settings, const char *font_name, const void *os_data) { -#if defined(WITH_FREETYPE) || defined(_WIN32) +#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) strecpy(settings->mono.font, font_name, lastof(settings->mono.font)); settings->mono.os_handle = os_data; #endif From b66e977acd6790d2e7c26d39f17ac5158b499167 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 13 Feb 2021 23:08:02 +0100 Subject: [PATCH 069/122] Change: [OSX] When auto-detecting a font, try for sans-serif first. On a display, especially with small fonts or low pixel sizes, sans-serif fonts are usually easier to read than serif fonts. --- src/os/macosx/font_osx.cpp | 67 +++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/src/os/macosx/font_osx.cpp b/src/os/macosx/font_osx.cpp index 4ce2b24e09..0a1b9e99ab 100644 --- a/src/os/macosx/font_osx.cpp +++ b/src/os/macosx/font_osx.cpp @@ -101,36 +101,43 @@ bool SetFallbackFont(FreeTypeSettings *settings, const char *language_isocode, i CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(lang_desc.get(), mandatory_attribs.get())); bool result = false; - for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()); i++) { - CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i); - - /* Get font traits. */ - CFAutoRelease traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute)); - CTFontSymbolicTraits symbolic_traits; - CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait), kCFNumberIntType, &symbolic_traits); - - /* Skip symbol fonts and vertical fonts. */ - if ((symbolic_traits & kCTFontClassMaskTrait) == (CTFontStylisticClass)kCTFontSymbolicClass || (symbolic_traits & kCTFontVerticalTrait)) continue; - /* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */ - if (symbolic_traits & kCTFontBoldTrait) continue; - /* Select monospaced fonts if asked for. */ - if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue; - - /* Get font name. */ - char name[128]; - CFAutoRelease font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute)); - CFStringGetCString(font_name.get(), name, lengthof(name), kCFStringEncodingUTF8); - - /* There are some special fonts starting with an '.' and the last - * resort font that aren't usable. Skip them. */ - if (name[0] == '.' || strncmp(name, "LastResort", 10) == 0) continue; - - /* Save result. */ - callback->SetFontNames(settings, name); - if (!callback->FindMissingGlyphs()) { - DEBUG(freetype, 2, "CT-Font for %s: %s", language_isocode, name); - result = true; - break; + for (int tries = 0; tries < 2; tries++) { + for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()); i++) { + CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i); + + /* Get font traits. */ + CFAutoRelease traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute)); + CTFontSymbolicTraits symbolic_traits; + CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait), kCFNumberIntType, &symbolic_traits); + + /* Skip symbol fonts and vertical fonts. */ + if ((symbolic_traits & kCTFontClassMaskTrait) == (CTFontStylisticClass)kCTFontSymbolicClass || (symbolic_traits & kCTFontVerticalTrait)) continue; + /* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */ + if (symbolic_traits & kCTFontBoldTrait) continue; + /* Select monospaced fonts if asked for. */ + if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue; + + /* Get font name. */ + char name[128]; + CFAutoRelease font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute)); + CFStringGetCString(font_name.get(), name, lengthof(name), kCFStringEncodingUTF8); + + /* Serif fonts usually look worse on-screen with only small + * font sizes. As such, we try for a sans-serif font first. + * If we can't find one in the first try, try all fonts. */ + if (tries == 0 && (symbolic_traits & kCTFontClassMaskTrait) != (CTFontStylisticClass)kCTFontSansSerifClass) continue; + + /* There are some special fonts starting with an '.' and the last + * resort font that aren't usable. Skip them. */ + if (name[0] == '.' || strncmp(name, "LastResort", 10) == 0) continue; + + /* Save result. */ + callback->SetFontNames(settings, name); + if (!callback->FindMissingGlyphs()) { + DEBUG(freetype, 2, "CT-Font for %s: %s", language_isocode, name); + result = true; + break; + } } } From 8906e9e0fd6dc7bd64f9ad2c633da6f110dbf921 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Wed, 6 Jan 2021 23:02:10 +0100 Subject: [PATCH 070/122] Codechange: Consistently use screen size and not driver resolution for determining window sizes. --- src/video/cocoa/cocoa_v.mm | 2 +- src/window.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 5493c06525..c1fb996dec 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -270,7 +270,7 @@ bool VideoDriver_Cocoa::ToggleFullscreen(bool full_screen) */ bool VideoDriver_Cocoa::AfterBlitterChange() { - this->ChangeResolution(_screen.width, _screen.height); + this->ChangeResolution(_cur_resolution.width, _cur_resolution.height); this->UpdatePalette(0, 256); return true; } diff --git a/src/window.cpp b/src/window.cpp index aea7bf3418..5e49aacb0f 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -2158,8 +2158,8 @@ void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen) * the resolution clamp it in such a manner that it stays within the bounds. */ int new_right = w->left + w->width + delta_x; int new_bottom = w->top + w->height + delta_y; - if (new_right >= (int)_cur_resolution.width) delta_x -= Ceil(new_right - _cur_resolution.width, std::max(1U, w->nested_root->resize_x)); - if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, std::max(1U, w->nested_root->resize_y)); + if (new_right >= (int)_screen.width) delta_x -= Ceil(new_right - _screen.width, std::max(1U, w->nested_root->resize_x)); + if (new_bottom >= (int)_screen.height) delta_y -= Ceil(new_bottom - _screen.height, std::max(1U, w->nested_root->resize_y)); } w->SetDirty(); @@ -3468,7 +3468,7 @@ void ReInitAllWindows() NetworkReInitChatBoxSize(); /* Make sure essential parts of all windows are visible */ - RelocateAllWindows(_cur_resolution.width, _cur_resolution.height); + RelocateAllWindows(_screen.width, _screen.height); MarkWholeScreenDirty(); } From 0fc763bc5597e04ee528a34ca2730f780d25fb5e Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Thu, 7 Jan 2021 21:18:25 +0100 Subject: [PATCH 071/122] Change: [OSX] Render screen in full native resolution on HiDPI displays. --- os/macosx/Info.plist.in | 2 ++ src/video/cocoa/cocoa_v.mm | 16 ++++++++++++++-- src/video/cocoa/cocoa_wnd.h | 3 +++ src/video/cocoa/cocoa_wnd.mm | 30 ++++++++++++++++++++++++++---- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/os/macosx/Info.plist.in b/os/macosx/Info.plist.in index 04d6ea6d33..359d28de8d 100644 --- a/os/macosx/Info.plist.in +++ b/os/macosx/Info.plist.in @@ -29,5 +29,7 @@ Copyright 2004-${CURRENT_YEAR} The OpenTTD team NSPrincipalClass NSApplication + NSHighResolutionCapable + True diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index c1fb996dec..c21edc7acd 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -322,6 +322,11 @@ void VideoDriver_Cocoa::GameSizeChanged() BlitterFactory::GetCurrentBlitter()->PostResize(); ::GameSizeChanged(); + + /* We need to store the window size as non-Retina size in + * the config file to get same windows size on next start. */ + _cur_resolution.width = [ this->cocoaview frame ].size.width; + _cur_resolution.height = [ this->cocoaview frame ].size.height; } /** @@ -491,7 +496,7 @@ void VideoDriver_Cocoa::Draw(bool force_update) /* Normally drawRect will be automatically called by Mac OS X during next update cycle, * and then blitting will occur. If force_update is true, it will be done right now. */ - [ this->cocoaview setNeedsDisplayInRect:dirtyrect ]; + [ this->cocoaview setNeedsDisplayInRect:[ this->cocoaview getVirtualRect:dirtyrect ] ]; if (force_update) [ this->cocoaview displayIfNeeded ]; } @@ -530,7 +535,7 @@ void VideoDriver_Cocoa::AllocateBackingStore() { if (this->window == nil || this->cocoaview == nil || this->setup) return; - NSRect newframe = [ this->cocoaview frame ]; + NSRect newframe = [ this->cocoaview getRealRect:[ this->cocoaview frame ] ]; this->window_width = (int)newframe.size.width; this->window_height = (int)newframe.size.height; @@ -743,6 +748,13 @@ void VideoDriver_Cocoa::GameLoop() CGImageRelease(fullImage); } +- (void)viewDidChangeBackingProperties +{ + [ super viewDidChangeBackingProperties ]; + + self.layer.contentsScale = [ driver->cocoaview getContentsScale ]; +} + @end #endif /* WITH_COCOA */ diff --git a/src/video/cocoa/cocoa_wnd.h b/src/video/cocoa/cocoa_wnd.h index fa7036396c..55f0561b77 100644 --- a/src/video/cocoa/cocoa_wnd.h +++ b/src/video/cocoa/cocoa_wnd.h @@ -38,6 +38,9 @@ extern NSString *OTTDMainLaunchGameEngine; /** Subclass of NSView to support mouse awareness and text input. */ @interface OTTD_CocoaView : NSView +- (NSRect)getRealRect:(NSRect)rect; +- (NSRect)getVirtualRect:(NSRect)rect; +- (CGFloat)getContentsScale; - (NSPoint)mousePositionFromEvent:(NSEvent *)e; @end diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index 089bfe8344..cd194f7128 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -417,6 +417,30 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel float _current_magnification; NSUInteger _current_mods; bool _emulated_down; + bool _use_hidpi; +} + +- (instancetype)initWithFrame:(NSRect)frameRect +{ + if (self = [ super initWithFrame:frameRect ]) { + self->_use_hidpi = [ self respondsToSelector:@selector(convertRectToBacking:) ] && [ self respondsToSelector:@selector(convertRectFromBacking:) ]; + } + return self; +} + +- (NSRect)getRealRect:(NSRect)rect +{ + return _use_hidpi ? [ self convertRectToBacking:rect ] : rect; +} + +- (NSRect)getVirtualRect:(NSRect)rect +{ + return _use_hidpi ? [ self convertRectFromBacking:rect ] : rect; +} + +- (CGFloat)getContentsScale +{ + return _use_hidpi && self.window != nil ? [ self.window backingScaleFactor ] : 1.0f; } /** @@ -483,15 +507,13 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel if ([ e window ] == nil) pt = [ self.window convertRectFromScreen:NSMakeRect(pt.x, pt.y, 0, 0) ].origin; pt = [ self convertPoint:pt fromView:nil ]; - pt.y = self.bounds.size.height - pt.y; - - return pt; + return [ self getRealRect:NSMakeRect(pt.x, self.bounds.size.height - pt.y, 0, 0) ].origin; } - (void)internalMouseMoveEvent:(NSEvent *)event { if (_cursor.fix_at) { - _cursor.UpdateCursorPositionRelative(event.deltaX, event.deltaY); + _cursor.UpdateCursorPositionRelative(event.deltaX * self.getContentsScale, event.deltaY * self.getContentsScale); } else { NSPoint pt = [ self mousePositionFromEvent:event ]; _cursor.UpdateCursorPosition(pt.x, pt.y, false); From e5c325364285b1e6e93afa2a0a79b1ec437f238e Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Thu, 7 Jan 2021 22:09:43 +0100 Subject: [PATCH 072/122] Add: [OSX] Setting to disable HiDPI graphics rendering. --- src/table/misc_settings.ini | 10 ++++++++++ src/video/cocoa/cocoa_wnd.mm | 6 ++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/table/misc_settings.ini b/src/table/misc_settings.ini index d46ebe9bb4..edc779cafd 100644 --- a/src/table/misc_settings.ini +++ b/src/table/misc_settings.ini @@ -9,6 +9,10 @@ extern std::string _config_language_file; static const char *_support8bppmodes = "no|system|hardware"; +#ifdef WITH_COCOA +extern bool _allow_hidpi_window; +#endif + static const SettingDescGlobVarList _misc_settings[] = { [post-amble] }; @@ -317,5 +321,11 @@ min = ZOOM_LVL_MIN max = ZOOM_LVL_OUT_4X cat = SC_BASIC +[SDTG_BOOL] +ifdef = WITH_COCOA +name = ""allow_hidpi"" +var = _allow_hidpi_window +def = true + [SDTG_END] diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index cd194f7128..afe2cece69 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -46,6 +46,8 @@ * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information. */ +bool _allow_hidpi_window = true; // Referenced from table/misc_settings.ini + @interface OTTDMain : NSObject @end @@ -417,13 +419,13 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel float _current_magnification; NSUInteger _current_mods; bool _emulated_down; - bool _use_hidpi; + bool _use_hidpi; ///< Render content in native resolution? } - (instancetype)initWithFrame:(NSRect)frameRect { if (self = [ super initWithFrame:frameRect ]) { - self->_use_hidpi = [ self respondsToSelector:@selector(convertRectToBacking:) ] && [ self respondsToSelector:@selector(convertRectFromBacking:) ]; + self->_use_hidpi = _allow_hidpi_window && [ self respondsToSelector:@selector(convertRectToBacking:) ] && [ self respondsToSelector:@selector(convertRectFromBacking:) ]; } return self; } From f5555a6d262a77f3754934447d7440df381f37d9 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sun, 14 Feb 2021 12:36:11 +0100 Subject: [PATCH 073/122] Fix 9c872192: [OSX] Clear mouse button emulation flag. --- src/video/cocoa/cocoa_wnd.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm index afe2cece69..30fa3969bc 100644 --- a/src/video/cocoa/cocoa_wnd.mm +++ b/src/video/cocoa/cocoa_wnd.mm @@ -564,6 +564,7 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel - (void)mouseUp:(NSEvent *)event { if (self->_emulated_down) { + self->_emulated_down = false; [ self rightMouseUp:event ]; } else { _left_button_down = false; From a2c3197f424daa6e02201d8304015b0eb67d0b04 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Fri, 8 Jan 2021 22:11:57 +0100 Subject: [PATCH 074/122] Codechange: Make the settings for min/max zoom early load settings. --- src/table/misc_settings.ini | 29 +++++++++++++++++++++++++++++ src/table/settings.ini | 27 --------------------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/table/misc_settings.ini b/src/table/misc_settings.ini index edc779cafd..be5088ec9e 100644 --- a/src/table/misc_settings.ini +++ b/src/table/misc_settings.ini @@ -5,6 +5,8 @@ ; [pre-amble] +static bool ZoomMinMaxChanged(int32 p1); + extern std::string _config_language_file; static const char *_support8bppmodes = "no|system|hardware"; @@ -24,6 +26,7 @@ SDTG_STR = SDTG_STR($name, $type, $flags, $guiflags, $var, $def, SDTG_SSTR = SDTG_SSTR($name, $type, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), +SDTC_VAR = SDTC_VAR($var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extra), SDTG_END = SDTG_END() [defaults] @@ -303,6 +306,32 @@ min = 0 max = UINT32_MAX cat = SC_EXPERT +[SDTC_VAR] +var = gui.zoom_min +type = SLE_UINT8 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +guiflags = SGF_MULTISTRING +def = ZOOM_LVL_MIN +min = ZOOM_LVL_MIN +max = ZOOM_LVL_OUT_4X +str = STR_CONFIG_SETTING_ZOOM_MIN +strhelp = STR_CONFIG_SETTING_ZOOM_MIN_HELPTEXT +strval = STR_CONFIG_SETTING_ZOOM_LVL_MIN +proc = ZoomMinMaxChanged + +[SDTC_VAR] +var = gui.zoom_max +type = SLE_UINT8 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +guiflags = SGF_MULTISTRING +def = ZOOM_LVL_MAX +min = ZOOM_LVL_OUT_8X +max = ZOOM_LVL_MAX +str = STR_CONFIG_SETTING_ZOOM_MAX +strhelp = STR_CONFIG_SETTING_ZOOM_MAX_HELPTEXT +strval = STR_CONFIG_SETTING_ZOOM_LVL_OUT_2X +proc = ZoomMinMaxChanged + [SDTG_VAR] name = ""gui_zoom"" type = SLE_UINT8 diff --git a/src/table/settings.ini b/src/table/settings.ini index 2e085058f2..e08cadafe9 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -38,7 +38,6 @@ static bool InvalidateAISettingsWindow(int32 p1); static bool RedrawTownAuthority(int32 p1); static bool InvalidateCompanyInfrastructureWindow(int32 p1); static bool InvalidateCompanyWindow(int32 p1); -static bool ZoomMinMaxChanged(int32 p1); static bool MaxVehiclesChanged(int32 p1); static bool InvalidateShipPathCache(int32 p1); @@ -2791,32 +2790,6 @@ strhelp = STR_CONFIG_SETTING_SOFT_LIMIT_HELPTEXT strval = STR_CONFIG_SETTING_SOFT_LIMIT_VALUE cat = SC_EXPERT -[SDTC_VAR] -var = gui.zoom_min -type = SLE_UINT8 -flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC -guiflags = SGF_MULTISTRING -def = ZOOM_LVL_MIN -min = ZOOM_LVL_MIN -max = ZOOM_LVL_OUT_4X -str = STR_CONFIG_SETTING_ZOOM_MIN -strhelp = STR_CONFIG_SETTING_ZOOM_MIN_HELPTEXT -strval = STR_CONFIG_SETTING_ZOOM_LVL_MIN -proc = ZoomMinMaxChanged - -[SDTC_VAR] -var = gui.zoom_max -type = SLE_UINT8 -flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC -guiflags = SGF_MULTISTRING -def = ZOOM_LVL_MAX -min = ZOOM_LVL_OUT_8X -max = ZOOM_LVL_MAX -str = STR_CONFIG_SETTING_ZOOM_MAX -strhelp = STR_CONFIG_SETTING_ZOOM_MAX_HELPTEXT -strval = STR_CONFIG_SETTING_ZOOM_LVL_OUT_2X -proc = ZoomMinMaxChanged - [SDTC_BOOL] var = gui.population_in_label flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC From 22f5aeab07cb1c67561b9e348e2c944740233607 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Fri, 8 Jan 2021 22:15:06 +0100 Subject: [PATCH 075/122] Feature: Automatic UI and font zoom levels when supported by the OS. --- src/gfx.cpp | 24 ++++++++++++++++++++++++ src/gfx_func.h | 1 + src/lang/english.txt | 2 ++ src/openttd.cpp | 1 + src/settings_gui.cpp | 18 +++++++++++------- src/table/misc_settings.ini | 16 ++++++++-------- src/video/video_driver.hpp | 21 ++++++++++++++++++++- src/zoom_type.h | 5 +++++ 8 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/gfx.cpp b/src/gfx.cpp index 54851d375f..786bbb25b5 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -59,6 +59,10 @@ static ReusableBuffer _cursor_backup; ZoomLevel _gui_zoom; ///< GUI Zoom level ZoomLevel _font_zoom; ///< Font Zoom level +int8 _gui_zoom_cfg; ///< GUI zoom level in config. +int8 _font_zoom_cfg; ///< Font zoom level in config. + + /** * The rect for repaint. * @@ -1876,3 +1880,23 @@ void SortResolutions() { std::sort(_resolutions.begin(), _resolutions.end()); } + +/** + * Resolve GUI zoom level, if auto-suggestion is requested. + */ +void UpdateGUIZoom() +{ + /* Determine real GUI zoom to use. */ + if (_gui_zoom_cfg == ZOOM_LVL_CFG_AUTO) { + _gui_zoom = static_cast(Clamp(VideoDriver::GetInstance()->GetSuggestedUIZoom(), _settings_client.gui.zoom_min, _settings_client.gui.zoom_max)); + } else { + _gui_zoom = static_cast(_gui_zoom_cfg); + } + + /* Determine real font zoom to use. */ + if (_font_zoom_cfg == ZOOM_LVL_CFG_AUTO) { + _font_zoom = static_cast(VideoDriver::GetInstance()->GetSuggestedUIZoom()); + } else { + _font_zoom = static_cast(_font_zoom_cfg); + } +} diff --git a/src/gfx_func.h b/src/gfx_func.h index 7ac26f29bb..8695fa0e28 100644 --- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -77,6 +77,7 @@ void UpdateWindows(); void DrawMouseCursor(); void ScreenSizeChanged(); void GameSizeChanged(); +void UpdateGUIZoom(); void UndrawMouseCursor(); /** Size of the buffer used for drawing strings. */ diff --git a/src/lang/english.txt b/src/lang/english.txt index 567d238203..88ef187027 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1004,6 +1004,7 @@ STR_GAME_OPTIONS_RESOLUTION_OTHER :other STR_GAME_OPTIONS_GUI_ZOOM_FRAME :{BLACK}Interface size STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_TOOLTIP :{BLACK}Select the interface element size to use +STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_AUTO :(auto-detect) STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_NORMAL :Normal STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_2X_ZOOM :Double size STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_4X_ZOOM :Quad size @@ -1011,6 +1012,7 @@ STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_4X_ZOOM :Quad size STR_GAME_OPTIONS_FONT_ZOOM :{BLACK}Font size STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_TOOLTIP :{BLACK}Select the interface font size to use +STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_AUTO :(auto-detect) STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_NORMAL :Normal STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_2X_ZOOM :Double size STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_4X_ZOOM :Quad size diff --git a/src/openttd.cpp b/src/openttd.cpp index a631bd852b..b68f378cf6 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -771,6 +771,7 @@ int openttd_main(int argc, char *argv[]) /* Initialize the zoom level of the screen to normal */ _screen.zoom = ZOOM_LVL_NORMAL; + UpdateGUIZoom(); NetworkStartUp(); // initialize network-core diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index e04c50ac34..8bf118e76c 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -58,6 +58,7 @@ static const StringID _autosave_dropdown[] = { }; static const StringID _gui_zoom_dropdown[] = { + STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_AUTO, STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_NORMAL, STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_2X_ZOOM, STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_4X_ZOOM, @@ -65,6 +66,7 @@ static const StringID _gui_zoom_dropdown[] = { }; static const StringID _font_zoom_dropdown[] = { + STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_AUTO, STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_NORMAL, STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_2X_ZOOM, STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_4X_ZOOM, @@ -291,16 +293,16 @@ struct GameOptionsWindow : Window { break; case WID_GO_GUI_ZOOM_DROPDOWN: { - *selected_index = ZOOM_LVL_OUT_4X - _gui_zoom; + *selected_index = _gui_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _gui_zoom + 1 : 0; const StringID *items = _gui_zoom_dropdown; for (int i = 0; *items != INVALID_STRING_ID; items++, i++) { - list.emplace_back(new DropDownListStringItem(*items, i, _settings_client.gui.zoom_min > ZOOM_LVL_OUT_4X - i)); + list.emplace_back(new DropDownListStringItem(*items, i, i != 0 && _settings_client.gui.zoom_min > ZOOM_LVL_OUT_4X - i + 1)); } break; } case WID_GO_FONT_ZOOM_DROPDOWN: { - *selected_index = ZOOM_LVL_OUT_4X - _font_zoom; + *selected_index = _font_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _font_zoom + 1 : 0; const StringID *items = _font_zoom_dropdown; for (int i = 0; *items != INVALID_STRING_ID; items++, i++) { list.emplace_back(new DropDownListStringItem(*items, i, false)); @@ -333,8 +335,8 @@ struct GameOptionsWindow : Window { case WID_GO_AUTOSAVE_DROPDOWN: SetDParam(0, _autosave_dropdown[_settings_client.gui.autosave]); break; case WID_GO_LANG_DROPDOWN: SetDParamStr(0, _current_language->own_name); break; case WID_GO_RESOLUTION_DROPDOWN: SetDParam(0, GetCurRes() == _resolutions.size() ? STR_GAME_OPTIONS_RESOLUTION_OTHER : SPECSTR_RESOLUTION_START + GetCurRes()); break; - case WID_GO_GUI_ZOOM_DROPDOWN: SetDParam(0, _gui_zoom_dropdown[ZOOM_LVL_OUT_4X - _gui_zoom]); break; - case WID_GO_FONT_ZOOM_DROPDOWN: SetDParam(0, _font_zoom_dropdown[ZOOM_LVL_OUT_4X - _font_zoom]); break; + case WID_GO_GUI_ZOOM_DROPDOWN: SetDParam(0, _gui_zoom_dropdown[_gui_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _gui_zoom_cfg + 1 : 0]); break; + case WID_GO_FONT_ZOOM_DROPDOWN: SetDParam(0, _font_zoom_dropdown[_font_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _font_zoom_cfg + 1 : 0]); break; case WID_GO_BASE_GRF_DROPDOWN: SetDParamStr(0, BaseGraphics::GetUsedSet()->name.c_str()); break; case WID_GO_BASE_GRF_STATUS: SetDParam(0, BaseGraphics::GetUsedSet()->GetNumInvalid()); break; case WID_GO_BASE_SFX_DROPDOWN: SetDParamStr(0, BaseSounds::GetUsedSet()->name.c_str()); break; @@ -538,7 +540,8 @@ struct GameOptionsWindow : Window { case WID_GO_GUI_ZOOM_DROPDOWN: GfxClearSpriteCache(); - _gui_zoom = (ZoomLevel)(ZOOM_LVL_OUT_4X - index); + _gui_zoom_cfg = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO; + UpdateGUIZoom(); UpdateCursorSize(); UpdateAllVirtCoords(); FixTitleGameZoom(); @@ -547,7 +550,8 @@ struct GameOptionsWindow : Window { case WID_GO_FONT_ZOOM_DROPDOWN: GfxClearSpriteCache(); - _font_zoom = (ZoomLevel)(ZOOM_LVL_OUT_4X - index); + _font_zoom_cfg = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO; + UpdateGUIZoom(); ClearFontCache(); LoadStringWidthTable(); UpdateAllVirtCoords(); diff --git a/src/table/misc_settings.ini b/src/table/misc_settings.ini index be5088ec9e..8cb6d4211f 100644 --- a/src/table/misc_settings.ini +++ b/src/table/misc_settings.ini @@ -334,19 +334,19 @@ proc = ZoomMinMaxChanged [SDTG_VAR] name = ""gui_zoom"" -type = SLE_UINT8 -var = _gui_zoom -def = ZOOM_LVL_OUT_4X -min = ZOOM_LVL_MIN +type = SLE_INT8 +var = _gui_zoom_cfg +def = ZOOM_LVL_CFG_AUTO +min = ZOOM_LVL_CFG_AUTO max = ZOOM_LVL_OUT_4X cat = SC_BASIC [SDTG_VAR] name = ""font_zoom"" -type = SLE_UINT8 -var = _font_zoom -def = ZOOM_LVL_OUT_4X -min = ZOOM_LVL_MIN +type = SLE_INT8 +var = _font_zoom_cfg +def = ZOOM_LVL_CFG_AUTO +min = ZOOM_LVL_CFG_AUTO max = ZOOM_LVL_OUT_4X cat = SC_BASIC diff --git a/src/video/video_driver.hpp b/src/video/video_driver.hpp index d4e7501342..bc445d92d1 100644 --- a/src/video/video_driver.hpp +++ b/src/video/video_driver.hpp @@ -13,6 +13,7 @@ #include "../driver.h" #include "../core/geometry_type.hpp" #include "../core/math_func.hpp" +#include "../zoom_type.h" #include extern std::string _ini_videodriver; @@ -105,6 +106,18 @@ public: */ virtual void EditBoxGainedFocus() {} + /** + * Get a suggested default GUI zoom taking screen DPI into account. + */ + virtual ZoomLevel GetSuggestedUIZoom() + { + float dpi_scale = this->GetDPIScale(); + + if (dpi_scale >= 3.0f) return ZOOM_LVL_NORMAL; + if (dpi_scale >= 1.5f) return ZOOM_LVL_OUT_2X; + return ZOOM_LVL_OUT_4X; + } + /** * Get the currently active instance of the video driver. */ @@ -113,11 +126,17 @@ public: } protected: - /* + /** * Get the resolution of the main screen. */ virtual Dimension GetScreenSize() const { return { DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT }; } + /** + * Get DPI scaling factor of the screen OTTD is displayed on. + * @return 1.0 for default platform DPI, > 1.0 for higher DPI values, and < 1.0 for smaller DPI values. + */ + virtual float GetDPIScale() { return 1.0f; } + /** * Apply resolution auto-detection and clamp to sensible defaults. */ diff --git a/src/zoom_type.h b/src/zoom_type.h index 2606e07677..0945e1cc09 100644 --- a/src/zoom_type.h +++ b/src/zoom_type.h @@ -15,6 +15,8 @@ static uint const ZOOM_LVL_SHIFT = 2; static uint const ZOOM_LVL_BASE = 1 << ZOOM_LVL_SHIFT; +static const int8 ZOOM_LVL_CFG_AUTO = -1; + /** All zoom levels we know. */ enum ZoomLevel : byte { /* Our possible zoom-levels */ @@ -48,6 +50,9 @@ enum ZoomLevel : byte { }; DECLARE_POSTFIX_INCREMENT(ZoomLevel) +extern int8 _gui_zoom_cfg; +extern int8 _font_zoom_cfg; + extern ZoomLevel _gui_zoom; extern ZoomLevel _font_zoom; #define ZOOM_LVL_GUI (_gui_zoom) From 33099355b8f89e9a2e87f3f7797b7366fc68f330 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sat, 13 Feb 2021 19:24:11 +0100 Subject: [PATCH 076/122] Codechange: Re-init GUI zoom only when really changed. --- src/settings_gui.cpp | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 8bf118e76c..6abc4260f8 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -538,24 +538,32 @@ struct GameOptionsWindow : Window { } break; - case WID_GO_GUI_ZOOM_DROPDOWN: - GfxClearSpriteCache(); - _gui_zoom_cfg = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO; - UpdateGUIZoom(); - UpdateCursorSize(); - UpdateAllVirtCoords(); - FixTitleGameZoom(); - ReInitAllWindows(); + case WID_GO_GUI_ZOOM_DROPDOWN: { + int8 new_zoom = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO; + if (new_zoom != _gui_zoom_cfg) { + GfxClearSpriteCache(); + _gui_zoom_cfg = new_zoom; + UpdateGUIZoom(); + UpdateCursorSize(); + UpdateAllVirtCoords(); + FixTitleGameZoom(); + ReInitAllWindows(); + } break; + } - case WID_GO_FONT_ZOOM_DROPDOWN: - GfxClearSpriteCache(); - _font_zoom_cfg = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO; - UpdateGUIZoom(); - ClearFontCache(); - LoadStringWidthTable(); - UpdateAllVirtCoords(); + case WID_GO_FONT_ZOOM_DROPDOWN: { + int8 new_zoom = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO; + if (new_zoom != _font_zoom_cfg) { + GfxClearSpriteCache(); + _font_zoom_cfg = new_zoom; + UpdateGUIZoom(); + ClearFontCache(); + LoadStringWidthTable(); + UpdateAllVirtCoords(); + } break; + } case WID_GO_BASE_GRF_DROPDOWN: this->SetMediaSet(index); From f137b3057a9f618fbdfd9dc2f7eee1ea576086ac Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Thu, 14 Jan 2021 23:11:21 +0100 Subject: [PATCH 077/122] Codechange: [Win32] Increase SDK version for 32-bit target. Use the same Windows XP target as for 64-bit. Current MSVC version will not produce a binary that works on anything earlier anyway. --- src/stdafx.h | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/stdafx.h b/src/stdafx.h index 7ee3ce2041..fe2dc8f612 100644 --- a/src/stdafx.h +++ b/src/stdafx.h @@ -157,22 +157,12 @@ /* Stuff for MSVC */ #if defined(_MSC_VER) # pragma once -# ifdef _WIN64 - /* No 64-bit Windows below XP, so we can safely assume it as the target platform. */ -# define NTDDI_VERSION NTDDI_WINXP // Windows XP -# define _WIN32_WINNT 0x501 // Windows XP -# define _WIN32_WINDOWS 0x501 // Windows XP -# define WINVER 0x0501 // Windows XP -# define _WIN32_IE_ 0x0600 // 6.0 (XP+) -# else - /* Define a win32 target platform, to override defaults of the SDK - * We need to define NTDDI version for Vista SDK, but win2k is minimum */ -# define NTDDI_VERSION NTDDI_WIN2K // Windows 2000 -# define _WIN32_WINNT 0x0500 // Windows 2000 -# define _WIN32_WINDOWS 0x400 // Windows 95 -# define WINVER 0x0400 // Windows NT 4.0 / Windows 95 -# define _WIN32_IE_ 0x0401 // 4.01 (win98 and NT4SP5+) -# endif +# define NTDDI_VERSION NTDDI_WINXP // Windows XP +# define _WIN32_WINNT 0x501 // Windows XP +# define _WIN32_WINDOWS 0x501 // Windows XP +# define WINVER 0x0501 // Windows XP +# define _WIN32_IE_ 0x0600 // 6.0 (XP+) + # define NOMINMAX // Disable min/max macros in windows.h. # pragma warning(disable: 4244) // 'conversion' conversion from 'type1' to 'type2', possible loss of data From f175e38666e2f21f5873e95ca52f5aa321e8d3bf Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Fri, 8 Jan 2021 22:17:04 +0100 Subject: [PATCH 078/122] Add: [Win32] Automatic zoom level suggestion for Win32 video driver. The zoom level suggestion is based on the DPI scaling set in Windows. We use 150% scaling as the threshold for 2X zoom and 300% scaling as the threshold for 4X zoom. --- src/video/win32_v.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ src/video/win32_v.h | 2 ++ 2 files changed, 42 insertions(+) diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp index c1b31d14ab..68e6572baa 100644 --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -1316,3 +1316,43 @@ Dimension VideoDriver_Win32::GetScreenSize() const { return { static_cast(GetSystemMetrics(SM_CXSCREEN)), static_cast(GetSystemMetrics(SM_CYSCREEN)) }; } + +float VideoDriver_Win32::GetDPIScale() +{ + typedef UINT (WINAPI *PFNGETDPIFORWINDOW)(HWND hwnd); + typedef UINT (WINAPI *PFNGETDPIFORSYSTEM)(VOID); + typedef HRESULT (WINAPI *PFNGETDPIFORMONITOR)(HMONITOR hMonitor, int dpiType, UINT *dpiX, UINT *dpiY); + + static PFNGETDPIFORWINDOW _GetDpiForWindow = nullptr; + static PFNGETDPIFORSYSTEM _GetDpiForSystem = nullptr; + static PFNGETDPIFORMONITOR _GetDpiForMonitor = nullptr; + + static bool init_done = false; + if (!init_done) { + init_done = true; + + _GetDpiForWindow = (PFNGETDPIFORWINDOW)GetProcAddress(GetModuleHandle(_T("User32")), "GetDpiForWindow"); + _GetDpiForSystem = (PFNGETDPIFORSYSTEM)GetProcAddress(GetModuleHandle(_T("User32")), "GetDpiForSystem"); + _GetDpiForMonitor = (PFNGETDPIFORMONITOR)GetProcAddress(LoadLibrary(_T("Shcore.dll")), "GetDpiForMonitor"); + } + + UINT cur_dpi = 0; + + if (cur_dpi == 0 && _GetDpiForWindow != nullptr && _wnd.main_wnd != nullptr) { + /* Per window DPI is supported since Windows 10 Ver 1607. */ + cur_dpi = _GetDpiForWindow(_wnd.main_wnd); + } + if (cur_dpi == 0 && _GetDpiForMonitor != nullptr && _wnd.main_wnd != nullptr) { + /* Per monitor is supported since Windows 8.1. */ + UINT dpiX, dpiY; + if (SUCCEEDED(_GetDpiForMonitor(MonitorFromWindow(_wnd.main_wnd, MONITOR_DEFAULTTOPRIMARY), 0 /* MDT_EFFECTIVE_DPI */, &dpiX, &dpiY))) { + cur_dpi = dpiX; // X and Y are always identical. + } + } + if (cur_dpi == 0 && _GetDpiForSystem != nullptr) { + /* Fall back to system DPI. */ + cur_dpi = _GetDpiForSystem(); + } + + return cur_dpi > 0 ? cur_dpi / 96.0f : 1.0f; // Default Windows DPI value is 96. +} diff --git a/src/video/win32_v.h b/src/video/win32_v.h index ef02e132e6..1849a9ebc9 100644 --- a/src/video/win32_v.h +++ b/src/video/win32_v.h @@ -43,6 +43,8 @@ public: protected: Dimension GetScreenSize() const override; + + float GetDPIScale() override; }; /** The factory for Windows' video driver. */ From 8d780e0607b8f313251710639fb2642f6e733b97 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Sun, 14 Feb 2021 13:26:46 +0100 Subject: [PATCH 079/122] Add: [OSX] Automatic zoom level suggestion for Cocoa video driver. --- src/video/cocoa/cocoa_v.h | 1 + src/video/cocoa/cocoa_v.mm | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h index 596a9e27d4..5dd3d1d1f4 100644 --- a/src/video/cocoa/cocoa_v.h +++ b/src/video/cocoa/cocoa_v.h @@ -73,6 +73,7 @@ public: protected: Dimension GetScreenSize() const override; + float GetDPIScale() override; private: bool PollEvent(); diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index c21edc7acd..4c6ce6cab2 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -294,6 +294,12 @@ Dimension VideoDriver_Cocoa::GetScreenSize() const return { static_cast(NSWidth(frame)), static_cast(NSHeight(frame)) }; } +/** Get DPI scale of our window. */ +float VideoDriver_Cocoa::GetDPIScale() +{ + return this->cocoaview != nil ? [ this->cocoaview getContentsScale ] : 1.0f; +} + /** * Are we in fullscreen mode? * @return whether fullscreen mode is currently used From 6b04b7cf187a17f8df027fe0134474b118c6a0d8 Mon Sep 17 00:00:00 2001 From: translators Date: Sun, 14 Feb 2021 19:01:09 +0000 Subject: [PATCH 080/122] Update: Translations from eints slovak: 13 changes by FuryPapaya catalan: 7 changes by J0anJosep french: 1 change by arikover --- src/lang/catalan.txt | 8 +++++++- src/lang/french.txt | 2 +- src/lang/slovak.txt | 20 +++++++++++++------- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/lang/catalan.txt b/src/lang/catalan.txt index 926ee9f55f..5a754ff4fe 100644 --- a/src/lang/catalan.txt +++ b/src/lang/catalan.txt @@ -2606,12 +2606,18 @@ STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM :{BLACK}Aleatori # Fund new industry window STR_FUND_INDUSTRY_CAPTION :{WHITE}Finança una nova indústria STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}Escolleix el tipus d'indústria adequada d'aquesta llista -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :Moltes indústries aleatòries +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :{BLACK}Crea indústries aleatòries STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Omple el mapa amb indústries situades aleatòriament +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION :{WHITE}Crea indústries aleatòries +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY :{YELLOW}Esteu segur que voleu crear moltes indústries aleatòries? STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST :{BLACK}Cost: {YELLOW}{CURRENCY_LONG} STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY :{BLACK}Prospecciona STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}Construeix STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}Finança +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES :{BLACK}Treu totes les indústries +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP :{BLACK}Treu totes les indústries que hi ha actualment al mapa. +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION :{WHITE}Treu totes les indústries +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY :{YELLOW}Esteu segur que voleu treure totes les indústries? # Industry cargoes window STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION :{WHITE}Cadena industrial per la indústria {STRING} diff --git a/src/lang/french.txt b/src/lang/french.txt index 868063acb8..198e6bd4db 100644 --- a/src/lang/french.txt +++ b/src/lang/french.txt @@ -4696,7 +4696,7 @@ STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_1 :Immeuble de bur STR_TOWN_BUILDING_NAME_SMALL_BLOCK_OF_FLATS_1 :Petit immeuble résidentiel STR_TOWN_BUILDING_NAME_CHURCH_1 :Église STR_TOWN_BUILDING_NAME_LARGE_OFFICE_BLOCK_1 :Gros immeuble de bureaux -STR_TOWN_BUILDING_NAME_TOWN_HOUSES_1 :Bâtiments municipaux +STR_TOWN_BUILDING_NAME_TOWN_HOUSES_1 :Maisons de ville STR_TOWN_BUILDING_NAME_HOTEL_1 :Hôtel STR_TOWN_BUILDING_NAME_STATUE_1 :Statue STR_TOWN_BUILDING_NAME_FOUNTAIN_1 :Fontaine diff --git a/src/lang/slovak.txt b/src/lang/slovak.txt index 565b811678..4562072e75 100644 --- a/src/lang/slovak.txt +++ b/src/lang/slovak.txt @@ -2645,7 +2645,7 @@ STR_FOUND_TOWN_NEW_TOWN_TOOLTIP :{BLACK}Založi STR_FOUND_TOWN_RANDOM_TOWN_BUTTON :{BLACK}Nahodné mesto STR_FOUND_TOWN_RANDOM_TOWN_TOOLTIP :{BLACK}Postaviť mesto na náhodnej pozícii STR_FOUND_TOWN_MANY_RANDOM_TOWNS :{BLACK}Veľa náhodných miest -STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP :{BLACK}Pokryt mapu nahodne umiestnenymi mestami +STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP :{BLACK}Pokryť mapu náhodne umiestnenými mestami STR_FOUND_TOWN_NAME_TITLE :{YELLOW}Meno mesta: STR_FOUND_TOWN_NAME_EDITOR_TITLE :{BLACK}Vložte meno mesta @@ -2673,12 +2673,18 @@ STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM :{BLACK}Náhodn # Fund new industry window STR_FUND_INDUSTRY_CAPTION :{WHITE}Financovať nový priemysel STR_FUND_INDUSTRY_SELECTION_TOOLTIP :{BLACK}Vyberte vhodný typ priemyslu zo zoznamu -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :Veľa náhodného priemyslu -STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Pokryt mapu nahodne umiestnenym priemyslom +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES :{BLACK}Vytvoriť náhodný priemysel +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP :{BLACK}Pokryť mapu náhodne umiestneným priemyslom +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_CAPTION :{WHITE}Vytvoriť náhodný priemysel +STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_QUERY :{YELLOW}Ste si istý, že chcete vytvoriť veľké množstvo náhodného priemyslu? STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST :{BLACK}Cena: {YELLOW}{CURRENCY_LONG} STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY :{BLACK}Vyhladat -STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}Postavit -STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}Založit +STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY :{BLACK}Postaviť +STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY :{BLACK}Založiť +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES :{BLACK}Odstrániť všetok priemysel +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_TOOLTIP :{BLACK}Odstrániť všetok priemysel umiestnený na mape +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_CAPTION :{WHITE}Odstrániť všetok priemysel +STR_FUND_INDUSTRY_REMOVE_ALL_INDUSTRIES_QUERY :{YELLOW}Ste si istý, že chcete odstrániť všetok priemysel? # Industry cargoes window STR_INDUSTRY_CARGOES_INDUSTRY_CAPTION :{WHITE}Reťazec pre priemysel: {STRING} @@ -4444,11 +4450,11 @@ STR_ERROR_STATUE_NO_SUITABLE_PLACE :{WHITE}... v ce # Industry related errors STR_ERROR_TOO_MANY_INDUSTRIES :{WHITE}... príliš veľa priemyslu -STR_ERROR_CAN_T_GENERATE_INDUSTRIES :{WHITE}Nemozno vygenerovat priemysel ... +STR_ERROR_CAN_T_GENERATE_INDUSTRIES :{WHITE}Nemožno vygenerovať priemysel... STR_ERROR_CAN_T_BUILD_HERE :{WHITE}{STRING} sa tu nedá postaviť... STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY :{WHITE}Nemôžeš tu postaviť tento typ priemyslu... STR_ERROR_INDUSTRY_TOO_CLOSE :{WHITE}... príliš blízko iného priemyslu -STR_ERROR_MUST_FOUND_TOWN_FIRST :{WHITE}... najskor treba postavit mesto... +STR_ERROR_MUST_FOUND_TOWN_FIRST :{WHITE}... najskôr je potrebné založiť mesto STR_ERROR_ONLY_ONE_ALLOWED_PER_TOWN :{WHITE}... povolené len jedno pre 1mesto STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS_WITH_POPULATION_OF_1200 :{WHITE}... môže byť postavené v mestách z populáciou väčšou ako 1200 STR_ERROR_CAN_ONLY_BE_BUILT_IN_RAINFOREST :{WHITE}... môže byť postavené len v dažďovom pralese From a18188ae902b05aa355cd24e252e92d459e6e946 Mon Sep 17 00:00:00 2001 From: Joan Josep Date: Sun, 14 Feb 2021 23:09:01 +0100 Subject: [PATCH 081/122] Doc: Add labels to landscape grid description. (#8452) --- docs/landscape_grid.html | 425 +++++++++++++++++---------------------- 1 file changed, 184 insertions(+), 241 deletions(-) diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html index d4a88d0bb5..7eac768591 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -5,10 +5,14 @@ OpenTTD Landscape Internals - #2