Skocz do zawartości

Matthew11

Użytkownicy
  • Zawartość

    71
  • Rejestracja

  • Ostatnio

  • Wygrane dni

    2

Matthew11 wygrał w ostatnim dniu 9 stycznia

Matthew11 ma najbardziej lubianą zawartość!

Reputacja

74 Bardzo dobra

O Matthew11

  • Ranga
    4/10

Informacje

  • Płeć
    Mężczyzna
  • Lokalizacja
    Kraków
  • Języki programowania
    C, C++

Ostatnio na profilu byli

Blok z ostatnio odwiedzającymi jest wyłączony i nie jest wyświetlany innym użytkownikom.

  1. Wyręczę @koza_yo, problem był bardzo prosty, ale zdradliwy. Mianowicie @koza_yo stworzył swój projekt powiedzmy o nazwie X, do którego przekopiował zawartość plików z projektu dołączonego do artykułu czyli projektu o nazwie Qt_Forbot_BluetoothCommunication. Przekopiował również zawartość pliku .pro, w którym znajdowała się linijka "TARGET = Qt_Forbot_BluetoothCommunication". Czyli w projekcie X chciał zbudować program dla projektu Qt_Forbot_BluetoothCommunication przez co system budowania rzucał błędami, prawdopodobnie żeby zadziałało, należałoby zmienić linię (tą z TARGET) na "TARGET = X". Aby uniknąć takich błędów, poprawnym podejściem byłoby skopiowanie tylko zawartości plików mainwindow.h/cpp pod warunkiem, że nazywałyby się tak samo w obu projektach, a plik .pro zostawić nietknięty. Natomiast tworząc nowy projekt korzystając z nowszych wersji Qt (nie wiem konkretnie od której, ale w moim przypadku przy wersji 5.12.6) w pliku pro nie znajdziemy już definicji TARGET, jest ona prawdopodobnie zapożyczana z nazwy pliku .pro.
  2. Ok, spokojnie. Generalnie Qt składa się z wielu różnych modułów, np. QtCore, QtBluetooth, QtNetwork itp. I teraz w ramach każdego modułu masz X różnych klas, np. w ramach modułu QtBluetooth wspomnianą QBluetoothDeviceDiscoveryAgent. I teraz jak wiesz ze chcesz skorzystać w takiej klasy musisz najpierw poinformować system budowania żeby dołączył dany moduł, robisz to właśnie w pliku o nazwie projektu z rozszerzeniem .pro - (nazwaProjektu.pro). Jak otworzysz ten plik to masz np.: QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets I teraz chcąc skorzystać z klasy QBluetoothDeviceDiscoveryAgent to w dokumentacji tej klasy masz taką tabelkę gdzie znajdziesz (chodzi o wiersz z qmake) Header: #include <QBluetoothDeviceDiscoveryAgent> qmake: QT += bluetooth Since: Qt 5.2 Inherits: QObject Czyli wiesz, że musisz w pliku .pro dopisać: QT += core gui bluetooth I teraz jeśli chodzi o pliki tej klasy, to nie musisz ich brać znikąd, bo one będą już w postaci skompilowanych bibliotek dostarczone wraz z zainstalowaniem Qt, więc jedyne co robisz, to w pliku gdzie chcesz skorzystać z tej klasy np. w mainwindow.h, dodajesz: #include <QBluetoothDeviceDiscoveryAgent> Dokładnie tak samo było to pokazane w 4 części. Dla szybkiego testu, pobierz gotowy kod z 4 części i spróbuj go zbudować. Jeśli nadal będziesz miał problemy, to podaj jak najwięcej informacji i będziemy szukać.
  3. Z informacji, które podałeś strzelam, że chodzi o brak #include <QBluetoothDeviceDiscoveryAgent> w pliku mainwindow.h. Jeśli masz dołączoną klasę, a nadal masz taki błąd to może w pliku .pro brakuje QT += bluetooth.
  4. W takim razie, jeśli chcesz zbudować drona od totalnego zera - programowanie obsługi silników, komunikacji czy kontrolera lotu etc. - to możesz zacząć od tych filmów: dron na ATmedze 328p (w filmach płytka Arduino Uno): https://www.youtube.com/watch?v=XFxqFQwRumc&list=PL0K4VDicBzsibZqfa42DVxC8CGCMB7G2G dron na STM32F103 (w filmach płytka Blue Pill): https://www.youtube.com/watch?v=EeJeSE_5rns&list=PL0K4VDicBzshwCpUHzIB6hOLQVkDFHbxC Natomiast, jeśli chcesz kupić klocki i złożyć je w drona to najlepiej rozejrzeć się za gotowymi kontrolerami lotu i nie bawić się implementację własnych, a finalnie je tylko skonfigurować/dostroić. W tej pierwszej opcji można się bardzo dużo nauczyć, ale niekoniecznie musi się udać, w drugiej z kolei będziesz miał działającą zabawkę.
  5. Podstawy podstaw JS (zmienne, funkcje, operacje na stringach i tablicach, obiekty w JS) na pewno będą przydatne, pozostałe problemy można rozwiązywać w trakcie nauki. Znajomość HTML i CSS nie jest wymagana. Za dokumentacją Qt: Dobre wprowadzenie do QML znajdziemy w dokumentacji. I to powinno w zupełności wystarczyć.
  6. @Shog A więc po kolei: Nie jest wymagane. Rok temu, gdy pisałem części 1-4 używałem "this", teraz używam go tylko tam, gdzie jest konieczny albo w definicjach konstruktorów. Natomiast stosowanie o które pytasz - do metod i zmiennych wewnątrz klasy - to już powiedziałbym, że to kwestia gustu albo wytycznych coding rules. Tak. Możesz to sprawdzić w dokumentacji at(index), [index]. Zwróć uwagę, że operator [] jest dwukrotnie przeładowany w tej klasie. Zdecydowanie tak. Części 1--4 powstały prawie rok temu, teraz pewnie napisałbym większość inaczej/lepiej. Nie mam nic pod ręką co bym mógł polecić z literatury, może ktoś inny poleci coś wartego przeczytania. Ale zachęcam do własnych eksperymentów i rozpoczęcia od prostej rzeczy czyli założenia, że mamy bufor nadawczy (txBuffer) i odbiorczy (rxBuffer) i nasz pakiet składa się z kilku bajtów: Preambuły (np. 'Q', 't'), Rozmiaru, Komendy i na początek 4 bajtów danych. I założenia, że rozmiar pakietu jest stały. Ramka będzie wtedy wyglądać tak: +----------------------+---------+---------+---------+ | Preambuła ('Q', 't') | Rozmiar | Komenda | Dane | +----------------------+---------+---------+---------+ | 2 bajty | 1 bajt | 1 bajt | 4 bajty | +----------------------+---------+---------+---------+ I teraz zakładając, że chcemy przesłać pakiet z numerem komendy = 1 i 4 bajty danych postaci 0xAA5500FF - to pakiet o rozmiarze 4 bajtów (licząc tylko dane) będzie miał następujące wartości: +-----+-----+---+---+------+------+------+------+ | 'Q' | 't' | 4 | 1 | 0xAA | 0x55 | 0x00 | 0xFF | +-----+-----+---+---+------+------+------+------+ Preambułę specjalnie zapisałem w postaci znaków ASCII (w formacie heksadecymalnym miałaby wartości 0x51 oraz 0x74), a Rozmiar i Komendę zapisałem w formacie decymalnym (w formacie heksadecymalnym miały by wartości odpowiednio 0x04 i 0x01). I teraz odebrane bajty "zapisujesz" w buforze rxBuffer robiąc, to w ten sposób, że wpisujesz na sztywno (oczywiście na czas rozwoju) w ten bufor wartości z tabelki powyżej. I teraz piszesz sobie obsługę analizy bufora odbiorczego w poszukiwaniu początku pakietu - preambuły i następnie rozkodowania odnalezionego pakietu w buforze - czyli określenia jaki rozmiar ma pakiet, jaką przesyła komendę i jakie dane. Możesz to zrobić na wiele sposobów, możesz sobie stworzyć strukturę, która będzie reprezentować pakiet etc. Możesz też tutaj wykorzystać np. maszyny stanów - masz pełną dowolność w tym jak osiągniesz zamierzony efekt. ... I dalej musisz wziąć pod uwagę, takie czynniki jak to, że możesz odebrać kilka pakietów - jeden po drugim, albo odebrać kilka różnych - znowu wpisując na sztywno różne kombinacje danych do bufora odbiorczego - rozszerzając kod analizy pakietu. Kolejno będziesz musiał rozszerzyć możliwości buforu odbiorczego na bufor w postaci bufora kołowego, tak żeby nowe dane "doklejać" do tych które odebrałeś wcześniej i nadpisywać, te które zostały już przetworzone. Co wiąże się z rozszerzeniem funkcjonalności analizy bufora odbiorczego i wzięcia pod uwagę, że tym razem korzystamy z buforu kołowego - znowu wpisując na sztywno różne kombinacje danych do bufora odbiorczego, ale tym razem zakładając, że na ostatnim indeksie buforu znajduje się np. pierwszy bajt preambuły, a kolejna część pakietu na indeksie 0 buforu. ... Następnie musisz wziąć pod uwagę, że możesz odebrać "uszkodzony" pakiet np. zamiast 0xAA odebrać 0xEA, dlatego będziesz musiał dodać kilka kolejnych bajtów do ramki - czyli obliczoną wartość CRC dla pakietu i sprawdzać tym samym integralność danych. Czyli odczytaną wartość CRC (tą którą policzył układ, który wysłał dane) porównujesz z obliczoną wartością CRC dla bajtów odebranego pakietu (czyli odebrałeś te dane i teraz "u siebie" liczysz dla nich wartość CRC) - jeśli jest taka sama, oznacza, że dane, które odebrałeś nie są uszkodzone. +----------------------+---------+---------+---------+-----------------+ | Preambuła ('Q', 't') | Rozmiar | Komenda | Dane | CRC (np. CRC16) | +----------------------+---------+---------+---------+-----------------+ | 2 bajty | 1 bajt | 1 bajt | 4 bajty | 2 bajty | +----------------------+---------+---------+---------+-----------------+ Wartość CRC obliczana jest tutaj dla pól Preambuły, Rozmiaru, Komendy i Danych. Tutaj możesz spojrzeć na przykłady implementacji obliczania CRC. Czyli kolejny raz, będziesz musiał rozszerzyć analizę odebranych danych i ramki pakietu. ... Następnie musisz sobie napisać kod strony nadawczej i formowania pakietów (wraz z obliczaniem CRC itp). I faktyczne wysyłanie danych z bufora nadawczego i faktyczne odbieranie danych w buforze odbiorczym (co możesz potrzebować gdzieś wcześniej) - tak, aby pozbyć się tego ręcznego wpisywania danych w bufor i móc faktycznie skorzystać z napisanego protokołu. ... Następnie możesz dowolnie rozszerzać możliwości protokołu np. przez wprowadzenie zmiennego rozmiaru danych, czy gotowe funkcje/metody do tworzenia określonych pakietów, czy np. przesyłania danych w postaci typów uint16_t czy np. floatów - co wymaga np. użycia memcpy i tak dalej i tak dalej.
  7. @Wrona W samym błędzie masz podpowiedź co robisz źle, podajesz katalog w którym nie ma pliku wynikowego (.exe). Sprawdź w QtCreatorze gdzie jest katalog build twojego projektu (przycisk Projects i tam szukasz tego katalogu) - pisałeś, że jak klikałeś .exe to pojawiały się komunikaty o brakujących dll-kach, no to właśnie to jest ten katalog i jego musisz podać dla windeployqt.
  8. @Wrona dobrze, że o to zapytałeś bo to jest klasyczny problem, z którym spotykają się ludzie, gdy chcą dystrybuować swój program napisany w Qt. QtCreator jak uruchamia naszą aplikację to zapewnia, że wszystkie biblioteki DLL są odpowiednio podlinkowane. Jak chcemy uruchomić program z folderu to dostajemy właśnie komunikaty o braku dll-ek. Dla wszystkich zainteresowanych dystrybucją programów napisanych w Qt: Generalne uwagi: Najlepiej korzystać z gotowych narzędzi do dystrybucji, gdy w projekcie mamy masę plików np. qml, plików tłumaczeń, plików multimedialnych lub innych plików i korzystamy z wielu bibliotek Qt, to te narzędzia zadbają, o to aby te wszystkie pliki i biblioteki znalazły się w katalogu z naszym programem. Podczas budowania aplikacji przeznaczonej do dystrybucji, to pamiętajmy o tym, aby zbudować aplikację w trybie Release. Po użyciu narzędzia do dystrybucji, dobrze jest wyczyścić projekt w celu pozbycia się zbędnych plików w katalogu, gdzie została zbudowana nasza aplikacja. Wynikowy program (i wszystkie pozostałe, konieczne pliki) gotowy do dystrybucji znajdziemy w katalogu "build" (jego lokalizację znajdziemy w QtCreatorze pod przyciskiem Projekty na pasku po lewej stronie, w polu Katalog wersji). Wtedy dystrybuujemy cały katalog (Windows, Linux) lub plik .apk (Android). Windows: Korzystamy z narzędzia windeployqt, znajduje się ono w katalogu gdzie zainstalowaliśmy Qt i kolejno w katalogu odpowiedniego kompilatora. Musimy zwrócić uwagę jakim kompilatorem budujemy naszą aplikację, standardowo będzie to pewnie MinGW, ale istotne jest też czy budujemy aplikację 32 bitową czy 64 bitową, jak w QtCreatorze budujemy 64 bitową to przy deploy'u używamy narzędzia windeployqt z katalogu kompilatora 64 bitowego. Przykładowo w QtCreatorze używam kompilatora MinGW 64bit, szukam katalogu gdzie zainstalowałem Qt - u mnie przykładowo: D:\Dev\Qt\5.12.6\mingw73_64\bin tam znajdziemy między innymi narzędzie windeployqt (znajdują się tam też inne narzędzia), jak budowałbym 32bitową aplikację to używałbym windeployqt z katalogu ..\mingw73_32\bin\ Aby uruchomić narzędzie uruchamiamy konsolę Windows (cmd, albo powershell) i przechodzimy do katalogu z narzędziem windeployqt i wywołujemy komendą: dla projektu QtWidgets: windeployqt <path-to-app-binary> dla projektu QtQuick: windeployqt --qmldir <path-to-app-qml-files> <path-to-app-binary> Dla przykładu, katalog ze źródłami projektu mam w katalogu D:\Dev\Projects\TestDesktop natomiast katalog ze zbudowanym programem (katalog "build") mam w katalogu: D:\Dev\Projects\build-TestDesktop-Desktop_Qt_5_12_6_MinGW_64_bit-Release\release i zakładam, że buduję aplikację QtQuick kompilatorem MinGW 64bit oraz zakładam, że wcześniej znalazłem się w katalogu D:\Dev\Qt\5.12.6\mingw73_64\bin wykorzystując powershell'a to wywołanie będzie takie: .\windeployqt.exe --qmldir D:\Dev\Projects\TestDesktop D:\Dev\Projects\build-TestDesktop-Desktop_Qt_5_12_6_MinGW_64_bit-Release\release Następnie uruchamiam program (.exe) i patrzę jakich plików dll brakuje - windeployqt nie dodaje kilku plików np: libgcc_s_seh-1.dll libwinpthread-1.dll libstdc++-6.dll Brakujące pliki znajdują się w folderze z narzędziem windeployqt czyli w przykładzie: D:\Dev\Qt\5.12.6\mingw73_64\bin. Należy je skopiować do folderu z programem (.exe), następnie program powinien już uruchamiać się bez problemu. I dopiero cały katalog D:\Dev\...\release możemy już dystrybuować wraz z naszym programem. Więcej informacji znajdziemy w dokumentacji. Linux (Ubuntu): Na tym samym komputerze, nie musimy robić nic z bibliotekami linkowanymi dynamicznie. Możemy program uruchomić prosto z folderu build. Gdy chcemy przenieść program na inny komputer to możemy skorzystać z narzędzia linuxdeployqt lub zobaczyć co na ten temat mówi dokumentacja. Android: QtCreator tworzy wynikowy plik .apk, który ma dołączone wszystkie wymagane biblioteki, więc dystrybuujemy sam instalator w postaci pliki .apk. Plik ten znajdziemy w otchłani folderu build. Ostatnia najważniejsza rzecz, o której musimy pamiętać przy dystrybucji naszych programów to spełnienie wszystkich warunków licencji, z których korzystają wykorzystane przez nas biblioteki.
  9. Generalnie zmienić każde wystąpienie "MainWindow" na "Inne" - najlepiej użyć globalnego "zamień na" powinna być taka opcja w QtCreatorze, (możliwe, że w pliku .ui, też coś jest wykorzystane, nie pamiętam trzeba sprawdzić), zmienić prawdopodobnie nazwy plików mainwindow.h/cpp na inne.h/cpp, zmienić #include, które includują mainwindow.h na include inne.h/cpp. Może wtedy kompilacja się uda, jak nie to patrz co mówią błędy. Ostatecznie możesz stworzyć projekt od nowa z odpowiednią nazwą (ale to dużo więcej pracy).
  10. @Wrona Ok, należy zacząć od tego jak po stronie mikrokontrolera są wysyłane dane: czy a) jest to kilka paczek wysyłanych w określonych odstępach czasu na jedno kliknięcie wyślij czy b) jedna dłuższa paczka wysłana raz na jedno kliknięcie wyślij. Jeśli a) to musisz określić najdłuższy z tych odstępów załóżmy, że 200ms, to bezpiecznie po stronie programu ustalasz, że jak dane nie pojawiają się od ostatnich 250ms to oznacza, że transmisja się zakończyła i można odblokować przycisk. Jeśli opcja b) to pod warunkiem, że nic nie blokuje wysyłania paczki danych po stronie mikrokontolera i zaczyna on wysyłać dane natychmiast po odebraniu komendy z PC to możesz śmiało założyć jakiś krótki okres czasu, że np. po czasie 20ms (lub krótszym) po ostatnim odebranym bajcie możesz odblokować przycisk. I teraz po stronie aplikacji istotne są dwie rzeczy: 1) zablokowanie przycisku przy kliknięciu, 2) obsługa timera w slocie odbierania danych. Czyli klikasz przycisk -> zablokuj go i uruchom timer z odpowiednim interwałem dostosowanym do twojego układu. Następnie w slocie odbierania danych resetujesz timer - dlaczego? Dlatego, że do tego slotu możesz wejść kilka razy, na jedną paczkę danych - i nie obchodzi Cię ile razy tam wejdziesz, tylko to, żeby resetować timer dopóki dane są odbierane - i teraz jak wszystkie dane zostaną odebrane to już slot odbioru danych nie będzie wywoływany, a tym samym nie zrestujesz timera, który ostatecznie wyemituje sygnał triggered() w którym odblokujesz przycisk. (Natomiast najlepszą opcją było by tu wprowadzenie pakietowej transmisji, gdzie odebranie całego pakietu z danymi było by równoznaczne z zakończeniem transmisji, lub po prostu wysyłanie pakietu o jej zakończeniu.)
  11. Jak chcesz zablokować przycisk na jakiś określony czas to użyj klasy QTimer i stałego interwału, w dokumentacji, w opisie masz sposób użycia. Przycisk ma możliwość ustawienia opcji enabled. Czyli enabled = true - przycisk można klikać, enabled = false - przycisku nie możesz kliknąć (na interfejsie będzie szary). Sposób działania może być taki: przy kliknięciu wyślij blokujesz przycisk za pomocą setEnabled(false), uruchamiasz w tym samym czasie timer, gdy timer wyemituje sygnał triggered() to w slocie jego obsługi odblokowujesz przycisk przez setEnabled(true). Druga opcja to znowu zablokowanie przycisku w momencie jego kliknięcia i uruchomienie timera 5 sekundowego tak jak napisałeś (lub o innym odpowiednim interwale) i resetowanie go w slocie odbioru danych przez wywołanie metody start() - zresetuje to timer. Gdy dane będą odbierane to timer będzie resetowany i przycisk nadal będzie zablokowany, natomiast jak skończy się transmisja to po 5 sekundach timer wyemituje sygnał (bo już nic nie będzie go resetować), następnie w slocie jego obsługi możesz odblokować przycisk co umożliwi ponowne użycie przycisku.
  12. Wzięte z półki, raczej na pewno nie zadziałają, przynajmniej nie od razu. Arduino to od dawna już nie tylko płytki i biblioteki ale ogromny framework jak inne, gdzie masz setki różnych interfejsów i określonych API. Jeśli twój procesor ma wsparcie Arduino (ktoś napisał drivery), to po dołączeniu wszystkich zależności i bibliotek do peryferiów, które ta konkretna biblioteka potrzebuje raczej powinno ostatecznie zadziałać. Tylko właśnie problem w tym, że tych zależności może być sporo, które mogą wymagać kolejnych. Inna sprawa to fakt, że większość z tych bibliotek jest napisana w C++.
  13. Jak ma być zaawansowanie, ładnie i nowocześnie to ja polecam C++ i framework Qt, a konkretnie technologie QtQuick i QML do budowy interfejsów, a C++ zostaje na cześć bacekend'ową. Polecam Ci wpisać w wyszukiwarkę "built with qt" gdzie znajdziesz studium przypadków użycia Qt w komercyjnych projektach np. https://www.qt.io/ulstein-built-with-qt. Sam na co dzień korzystam z tych technologii i szczerze mówiąc nigdy nie pomyślałem o próbie poszukania czegoś innego. Nie mam dużego doświadczenia ze wspomnianym GTK, ale uważam że wykorzystanie Qt jest znacznie wygodniejsze. Aktualnie pracujemy z @Treker nad kontynuacją kursu Qt i właśnie wszystko nowe (dotyczące interfejsu) będzie oparte w 95% na QtQuick i QML. Więc sama będziesz mogła ocenić czy to jest to czego oczekujesz. Inną, dopiero rosnącą alternatywą jest język Dart i framework Flutter. Flutter to takie cross platformowe Qt stworzone z myślą o urządzeniach mobilnych ale nie tylko, które prawdopodobnie zagarnie część rynku.
  14. Musisz po prostu dodać kontrolkę do wprowadzania tekstu np. https://doc.qt.io/qt-5/qlineedit.html i w niej wpisywać imię badanego, której zawartość możesz na jakiś przycisk dodać do okna logów i/lub wysłać za pomocą metody, która została przedstawiona w artykule. Podałem Ci w poprzedniej wiadomości: "QTextEdit daje Ci dwie metody: toPlainText() i toHtml() obie zwracają QString'a", czyli biorąc kod z artykułu ui->textEditLogs->toPlainText(); i z tym robisz już to co w przykładzie z zapisem do pliku. I tak jak napisał @atMegaTona niestety, ale bez odpowiedniej wiedzy C++ i podstaw OOP nie zrobisz dużych postępów i nie będziesz w stanie dobrze wykorzystać Qt. Nie wiem jaką Ty preferujesz metodę uczenia, ale tutaj masz darmowy kurs od Johna Purcella do podstaw C++ (składnia, podstawy programowania obiektowego, dziedziczenie, zarządzanie pamięcią itp). Pomijając projekt na końcu kursu (5h) i podstawy (1h) zostaje około 12 godzin, który robiąc na przyspieszeniu zajmie Ci około tygodnia poświęcając ~1h dziennie.
  15. Cześć @Wrona Metoda MainWindow::sendMessageToDevice(QString message) właśnie zapewnia, Ci taką funkcjonalność, mamy na sztywno w kodzie: this->sendMessageToDevice("1"); a możesz zrobić w slocie pod dowolnym przyciskiem (generalnie możesz tam wysłać to co chcesz): this->sendMessageToDevice("Mateusz"); wtedy powinieneś po drugiej stronie odebrać cały string. Okej, to można zrobić na wiele sposobów w zależności od celu jaki chcesz osiągnąć, chyba najprostszy moim zdaniem to zapisanie zawartości okna z logami do pliku. QTextEdit daje Ci dwie metody: toPlainText() i toHtml() obie zwracają QString'a. W twoim przypadku chyba odpowiedniejsza będzie toPlainText(), wtedy używając klasy QFile, możesz takiego QStringa, wcześniej konwertując do QByteArray zapisać do pliku, przykład poniżej: #include <QCoreApplication> #include <QFile> #include <QString> #include <QDebug> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QFile file("logs.txt"); // Uwaga! Plik będzie stworzony w katalogu, gdzie znajdują się pliki wynikowe programu (build dir) if(!file.open(QIODevice::WriteOnly)) { qDebug() << "Could not open!"; } else { const QString dataToWrite("Mateusz"); file.write(dataToWrite.toUtf8()); file.close(); } return a.exec(); } W twoim programie, musisz wydostać zawartość okna z logami i wykorzystać powyższe. Więcej info znajdziesz w dokumentacji.
×
×
  • Utwórz nowe...