Archiwa tagu: cpp

Jenkins – konfiguracja projektu Qt [Windows]

Jenkins jest narzędziem do automatycznego budowania i testowania aplikacji. Ten wpis opisuje jak skonfigurować Jenkinsa do budowania i testowaniu aplikacji napisanych w C++ i Qt.
Docelowo Jenkins będzie pobierał projekt z gita, zbuduje go i odpali projekt z testami jednostkowymi, który jest aplikacją konsolową.

Nowy projekt.
Z menu na stronie głównej wybieramy New Item, podajemy nazwę projektu i klikamy Freestyle project i OK.

W karcie Source Code Management wybieramy git i podajemy adres repozytorium, np.
https://github.com/ololuki/Nonograms

Dobrze jest zaznaczyć checkbox „Delete workspace before build starts” (wymagany plugin „Workspace Cleanup”). Dzięki temu projekt zostanie za każdym razem wyczyszczony i zbudowany od zera, co pozwoli uniknąć dziwnych błędów kompilacji w przypadku dużych zmian w projekcie.

W karcie build dodajemy Add build step -> Execute Windows batch command
Dla kompilatora Visual C++ 2015 x64:

:: Konfiguracja środowiska
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64

:: Uruchomienie qmake
"D:\Qt\5.8\msvc2015_64\bin\qmake.exe" "nonograms.pro" -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug"

:: Przerwanie skryptu w przypadku błędu
if errorlevel 1 (
   echo Error: %errorlevel%
   exit /b %errorlevel%
)

:: Kompilacja projektu
"D:\Qt\Tools\QtCreator\bin\jom.exe" -f "Makefile"

:: Przerwanie skryptu w przypadku błędu
if errorlevel 1 (
   echo Error: %errorlevel%
   exit /b %errorlevel%
)

:: Dodanie bibliotek Qt do PATHa, aby uruchomić aplikację z testami
SET PATH=%PATH%;D:\Qt\5.8\msvc2015_64\bin

:: Uruchomienie aplikacji konsolowej z testami
"bin\NonogramsTests.exe"

Zadania w pliku wsadowym w cmd.exe są domyślnie wykonywane linia po linii bez sprawdzania błędów, więc jeśli jedno z zadań się nie powiedzie to pozostałe i tak mogą zostać wykonane. Trzeba więc ręcznie zadbać o zatrzymanie skryptu w przypadku błędu jednego z zadań poprzez sprawdzenie wartości zmiennej „errorlevel”.

Automatyczne sprawdzanie zmian w repozytorium.

Aby Jenkins automatycznie sprawdzał czy nie ma nowych zmian można użyć zakładki „Build Triggers”, zaznaczyć checkbox „Poll SCM” i w polu „Schedule” wpisać:

H/15 * * * *

gdzie 15 oznacza liczbę minut pomiędzy kolejnymi odpytaniami repozytorium.

Ręczne budowanie konkretnej wersji.

Aby umożliwić ręczne budowanie konkretnej wersji projektu za pomocą Jenkinsa w zakładce „General” zaznaczamy opcję „This project is parametrized” i w polu Name podajemy nazwe parametru, np. „commitId” – w naszym przepadku jest to hash commitu. Aby umożliwić budowanie wersji o podanym hashu w zakładce „Source Code Management” w polu „Branches to build” wpisujemy:

${commitId}

Od tej chwili po wejściu do projektu zamiast przycisku „Build Now” pojawi się przycisk „Build with Parameters” umożliwiający wpisanie hasha commita do zbudowania.

Diamentowy problem i dziedziczenie wirtualne

Język C++ oferuje możliwość dziedziczenia wielokrotnego ze wszystkimi wadami i zaletami tego rozwiązania. W przypadku dziedziczenia po kilku klasach mających wspólnego przodka może pojawić się problem z diamentem.

Błąd „base class is ambiguous” świadczy o tym, że dwie klasy, po których dziedziczy nasza „najmłodsza klasa” są potomkami wspólnego przodka. Jeśli obie te klasy dziedziczą po tym przodku niewirtualnie to otrzymamy wspomniany wcześniej błąd, ponieważ klasa dziadka zostanie dodana dwa razy, więc będzie zduplikowana. Rozwiązaniem problemu będzie zastosowane dziedziczenia wirtualnego w naszych dwóch klasach.

W moim projekcie dziedziczenie wirtualne zostało zastosowane przy dziedziczeniu podstawowej klasy pola – RootField. Klasa RootField przechowuje tylko główne, wspólne parametry pola takie jak szerokość i wysokość. Następnie mamy dwie klasy po niej dziedziczące – DrawingAreaField oraz BlocksDescriptionField. Jeden dostarcza metody dla obszaru rysowania (DrawingArea) a drugi dla opisów bloków (RowsDescription i ColumnsDescription). Klasa WholeField łączy te elementy w całość i pozwala odwoływać się do pola roboczego jako całości, np. z poziomu solvera.

class RootField : public QObject
{
	Q_OBJECT
public:
	RootField();
	RootField(size_t width, size_t height) : width(width), height(height) {}
	size_t getWidth() {return width;}
	size_t getHeight() {return height;}
signals:
	void dataChanged();
private:
	size_t width;
	size_t height;
};


class DrawingAreaField : public virtual RootField
{
public:
	virtual Pixel getPixel(AddressOnDrawingArea address) = 0;
	virtual void setPixel(Pixel pixel) = 0;
};


class BlocksDescriptionField : public virtual RootField
{
public:
	virtual BlockDescription getBlockDescription(AddressOnBlocksDescription address) = 0;
	virtual void updateBlockDescription(BlockDescription blockDescription) = 0;
	virtual void insertDescriptionBefore(BlockDescription blockDescription) = 0;
	virtual void addDescriptionAtEnd(BlockDescription blockDescription) = 0;
	virtual void deleteDescription(BlockDescription blockDescription) = 0;
	virtual size_t numberOfBlocksInColumn(size_t columnNumber) = 0;
	virtual size_t columnsDescriptionHeight() = 0;
	virtual bool isDefinedColumnDescriptionAt(size_t line, size_t count) = 0;
};


class WholeField : public DrawingAreaField, public BlocksDescriptionField, virtual public RootField
{
};


class WholeFieldImpl : public WholeField
{
public:
	WholeFieldImpl(size_t width, size_t height) : RootField(width, height)
	{
		array = ArrayOfPixels(width, height);
		columnsDescription = AllLinesDescription(AddressOnBlocksDescription::orientation::VERTICAL, width);
	}
	virtual Pixel getPixel(AddressOnDrawingArea address) override;
	virtual void setPixel(Pixel pixel) override;
	virtual BlockDescription getBlockDescription(AddressOnBlocksDescription address) override;
	virtual void updateBlockDescription(BlockDescription blockDescription) override;
	virtual void insertDescriptionBefore(BlockDescription blockDescription) override;
	virtual void addDescriptionAtEnd(BlockDescription blockDescription) override;
	virtual void deleteDescription(BlockDescription blockDescription) override;
	virtual size_t numberOfBlocksInColumn(size_t columnNumber) override;
	virtual size_t columnsDescriptionHeight() override;
	virtual bool isDefinedColumnDescriptionAt(size_t line, size_t count) override;
private:
	ArrayOfPixels array;
	AllLinesDescription columnsDescription;
};

Kolejnym problemem okazało się wywoływanie konstruktorów parametrycznych z klasy bazowej (dziedziczonej wirtualnie) za pomocą listy inicjalizacyjnej. Okazało się, że wywołanie konstruktora klasy bazowej (dziedziczonej wirtualnie) za pomocą listy inicjalizacyjnej musi znajdować się w najmłodszej klasie – w tej, której obiekt zostanie utworzony za pomocą konstruktora parametrycznego. W sytuacji gdy mamy hierarchię klas, np:

class A {
	int i;
public:
	A(int k) {}
};

class B : virtual public A {
public:
	B(int i) : A(i) {}
};

class C :   public B {
public:
	//C(int i) : B(i) {} // zostanie wywołany tylko konstruktor klasy B, konstruktor A(int k) nie zostanie wywołany, gdyż klasa A jest wirtualnie dziedziczona, zostanie wywołany konstruktor domyślny klasy A
	C(int i) : B(i), A(i) {}	// zostanę wywołane oba konstruktory parametryczne
};

Linki na temat:
multiple-inheritance-from-two-derived-classes
how-can-i-avoid-the-diamond-of-death-when-using-multiple-inheritance
Multiple_inheritance#The_diamond_problem
Virtual_inheritance

Wywoływanie konstruktorów z klasy wirtualnie dziedziczonej za pomocą listy inicjalizacyjnej.
order-of-constructor-call-in-virtual-inheritance

Qt Creator – „Error while saving file: Cannot write file … Disk full?” – rozwiązanie

Czasami podczas próby skompilowania i uruchomienia programu w Qt Creatorze pojawia się błąd „Error while saving file: Cannot write file … Disk full?”. Jest to objaw błędu w Clang Code Model. Pomaga wtedy zabicie procesu clangbackend.exe. Czasami też pojawiają się dziwne pliki w katalogu z plikami źródłowymi o rozszerzeniach złożonych z losowych znaków, np. *.zA5996 – trzeba je usunąć, bo mogą powodować problemy przy kompilacji.

Catch – problemy przy testowaniu plików *.cpp – unresolved external symbol

Pojawiły się pewne problemy przy testowaniu plików *.cpp. Dotychczas wszystkie pliki aplikacji objęte testami były zdefiniowane w plikach nagłówkowych *.h, więc problem się nie ujawniał, bo nie był potrzebny żaden kod z plików *.cpp. Dzisiaj postanowiłem napisać test do dość rozbudowanej klasy, której konstruktor został zdefiniowany w pliku *.cpp i niestety pojawił się błąd „unresolved external symbol”. Trochę czasu zajęło mi znalezienie przyczyny, którą był brak dołączonych plików *.cpp zawierających definicje konstruktorów i metod. Rozwiązaniem problemu było dołączenie plików *.cpp aplikacji do projektu test.pro.

Czytaj dalej

Lepsze podpowiadanie składni kodu w Qt Creatorze, czyli ClangCodeModel

Qt Creator domyślnie ma włączone „jakieś” podpowiadanie kodu, które cośtam podpowiada, jednak nie jest ono zbyt dobre. Problem pojawia się, gdy przeciążymy operator[], aby zwracał obiekt z naszego kontenera. Przykład:

// mamy przykładową klasę:
class Pixel
{
public:
	Pixel(int x, int y, bool isVisible = true) : x(x), y(y), visible(isVisible) {}
	bool isVisible(){return visible;}
	void makeVisible() {visible = true;}
	void makeInvisible() {visible = false;}
private:
	int x;
	int y;
	bool visible;
};

// i kontener na obiekty tej klasy:
class LineOfPixels : private std::vector < Pixel >
{
public:
	LineOfPixels(){}
	LineOfPixels(std::vector < Pixel > vectorToCopy) : std::vector< Pixel >(vectorToCopy) {}
	size_t size() {return std::vector < Pixel > ::size();}
	
	Pixel& getPixelAt(int pixelNumber) {return this->at(pixelNumber);}
	Pixel& operator[](const int pixelNumber) {return std::vector < Pixel > ::operator[](pixelNumber);}
};

int main()
{
	int size = 2;
	LineOfPixels lineOfPixels(std::vector <Pixel>(size, Pixel(0, 0)));
	lineOfPixels[1].makeInvisible();
	lineOfPixels.getPixelAt(1).makeVisible();
	
	return 0;
}

Po instalacji Qt Creatora opcja Clang Code Model domyślnie jest wyłączona. Jeśli teraz w programie chcemy odwołać się do obiektu klasy Pixel przez zwykły getter:

lineOfPixels.getPixelAt(1).makeInvisible();

to podpowiadanie składni zadziała normalnie pokazując listę dostępnych składowych na każdym poziomie. Natomiast gdy zrobimy to samo przez przeciążony operator[] jak tutaj:

lineOfPixels[1].makeInvisible();

to podpowiadanie składni zgłupieje i nic nam nie pomoże. Po wpisaniu „lineOfPixels[1].” nie otrzymamy żadnej podpowiedzi.
Aby podpowiadanie składni działało poprawnie w każdych warunkach, trzeba włączyć plugin ClangCodeModel, zaznaczając opcję w Help → About Plugins… → C++ → ClangCodeModel.

Link: Parsing C++ Files with the Clang Code Model

Layout dla każdego (QWidgetu) czyli co zrobić, gdy kontrolki wewnątrz layoutu nie skalują się wraz z oknem

Dziś trochę o layoutach i definiowaniu interfejsu graficznego. Po dodaniu kilku kontrolek do layoutu zauważyłem, że podczas skalowania okna znajdujące się w jego wnętrzu kontrolki nie zmieniają swojego rozmiaru i kształtu. Efekt, który chciałem osiągnąć to skalowanie kontrolek wewnątrz okna wraz ze zmianą rozmiaru okna. Chwila w kwakania w Duck Duck Go zaprowadziła mnie do rozwiązania problemu. Mianowicie każde okno (QMainWindow) i każdy obszar typu QScrollArea powinien mieć ustawiony layout. W edytorze interfejsu po prawej stronie mamy okno z drzewem widżetów. Dla nowo stworzonego projektu zaraz pod elementem MainWindow znajduje się centralWidget. Jest to serce całego interfejsu, najważniejszy widget w oknie. Po jego lewej stronie znajduje się ikonka layoutu z czerwonym znacznikiem informującym, że layout nie został ustawiony. Aby ustawić layout wystarczy kliknąć prawym przyciskiem myszy na MainWindow → Lay Out → Lay Out Horizontaly (bądź inny wybrany przez nas layout). Można też tego dokonać klikając na MainWindow a następnie ikonkę Lay Out Horizontaly na pasku ponad oknem edycji GUI albo używając skrótu klawiszowego CTRL + H.

Po ustawieniu layoutu zawartość okna powinna zmieniać rozmiar razem z nim:

Simple Drawing Application – prosta aplikacja QT5 znaleziona w Internecie

Znalazłem w Internecie całkiem schludnie napisaną a jednocześnie w miarę prostą aplikację w QT 5 z użyciem widgetów. Aplikacja ta korzysta z frameworka testów jednostkowych Catch. Autor inspirował się książką Roberta C. Martina – Agile Software Development, Principles, Patterns, and Practices. Osobiście czytałem inną książkę Roberta C. Martina – Czysty kod, którą przy okazji mogę polecić gdyż w prosty sposób przedstawia jak pisać czytelny i łatwy do zrozumienia kod.
Link do aplikacji na GitHubie:
https://github.com/bruceoutdoors/DrawingApp