2007.04_Events Controller–kontroler zdarzeń_[Programowanie].pdf
(
1229 KB
)
Pobierz
439031205 UNPDF
dla programistów
Events Controller – kontroler zdarzeń
– kontroler zdarzeń
Piotr Pszczółkowski
W artykule chciałbym zaprezentować własną ideę kontrolera zdarzeń (zdarzenie – ang.
Event
). Idea
zdarzeń jest powszechnie znana i stosowana w systemach operacyjnych komputerów. Dla nas jednak
bardziej interesujący jest fakt, że systemy obsługi graicznego interfejsu użytkownika (GUI) w takich
środowiskach graicznych jak KDE czy GNOME także wykorzystują zdarzenia jako sposób komunikacji.
Programista piszący oprogramowanie dla tych środowisk powinien umieć je obsłużyć. Za zdarzenie
można uznać w zasadzie prawie wszystko, zazwyczaj jest to ruch myszką czy naciśnięcie klawisza.
jak by to było, gdybym zastosował po-
dobny mechanizm we własnych pro-
gramach, z deiniowanymi przeze mnie
zdarzeniami. Po namyśle stwierdziłem, że sterowanie
przebiegiem wykonania programu poprzez wysyłanie
i odbieranie zdarzeń ma same plusy. Nie dopatrzyłem
się minusów. Dzięki mechanizmowi zdarzeń kod pro-
gramu staje się krótszy, zdecydowanie mniej skompli-
kowany (i co za tym idzie – bardziej zrozumiały) oraz
dużo łatwiejszy w dalszej rozbudowie. Ponieważ piszę
programy przy użyciu biblioteki Qt irmy Trolltech, za-
prezentuję moją implementację, którą wykonałem z uży-
ciem mechanizmów oferowanych przez tę właśnie biblio-
tekę.
cuje w innej irmie. W takiej sytuacji musimy znać kogoś,
kto zna panią B. Jeszcze gorzej jest, jeśli ten łańcuch zna-
jomości się wydłuża. Zaczyna to przypominać zabawę
w „głuchy telefon“. Kolejne osoby przekazują informację
innym, znajdującym się w łańcuchu znajomości za nimi.
Aż do celu, czyli do pani B. Odpowiedź oczywiście mo-
Listing 1.
Przykład handlera
//********************************
// showEvent PRIVATE inherited
//********************************
void
DbgRegisters
::
showEvent
(
QShowEvent
*
const
in_event
)
{
DbgToolDialog
::
showEvent
(
in_event
);
// ( 1 )
// nasza wlasna reakcja na zdarzenie
..........
}
// end of showEvent
Jak to się zazwyczaj robi
Zanim przejdziemy do meritum, chciałbym przedstawić
pewną analogię ze świata ludzi. Nie ma problemu, gdy
pan A chce coś powiedzieć pani B, gdy ta siedzi w pra-
cy w pokoju obok niego (zakładamy, że nie ma telefonów
i e-maili). Sytuacja jest znacznie gorsza, jeśli pani B pra-
44
maj 2007
Events Controller
W
pewnym momencie zastanawiałem się,
dla programistów
Events Controller – kontroler zdarzeń
że wrócić tą samą drogą lub też przez łań-
cuszek innych znajomości. W programach
komputerowych jest podobnie. Jak wiado-
mo, żaden większy program nie wykonu-
je się jednym ciągiem (w funkcji main). Pro-
gramista rozdziela to, co ma wykonać pro-
gram, na logiczne segmenty, które zawar-
te są w funkcjach. Funkcje są to bloki kodu
przeznaczone do wykonania ściśle określo-
nych zadań. Programista poprzez wywoły-
wanie funkcji powoduje wykonywanie ko-
lejnych podzadań, koniecznych w określo-
nych sytuacjach dla prawidłowego wyko-
nania zadania postawionego przed progra-
mem jako całością. Obecnie w dobie obiek-
towości podział na podzadania poszedł da-
lej. Teraz określone zadania dotyczące kon-
kretnych obiektów ukryte są w klasach. Wy-
konanie określonych czynności odbywa się
poprzez interfejs udostępniany przez klasę.
Interfejs klasy to po prostu funkcje publicz-
ne, które można wywoływać z innych obiek-
tów i funkcji. Krótko mówiąc – przebieg wy-
konywania zadania głównego odbywa się
poprzez aktywowanie kolejnych obiektów
stosownego typu (klasa to typ). I tu poja-
wia się problem, nad którym chciałbym się
na chwilę zatrzymać. W każdym w miarę
dużym programie ciąg wywołań kolejnych
obiektów(/funkcji) może być bardzo, bar-
dzo długi. Tak długi, że czasami trzeba so-
bie uzmysłowić to stosownymi diagramami
UML. Zaczyna się pojawiać problem nad za-
panowaniem nad setkami lub tysiącami wy-
wołań różnych obiektów w różnych miej-
scach. Nie ma problemu, kiedy obiekt kla-
sy A wywołuje funkcje obiektu klasy B, je-
śli go zna, tzn. obiekt A sam utworzył obiekt
B (tzn. w obiekcie A utworzono obiekt B na
stosie lub poprzez new). Ale powstaje pro-
blem, jeśli chcemy wywołać funkcję obiek-
tu klasy M, który w hierarchii zależności
(A, B, C, ......., M) znajduje się daleko, dale-
ko. Obiekt klasy M nie jest znany w obiekcie
A. Sytuacja może się jeszcze bardziej skom-
plikować, jeśli to obiekt M chce wywoływać
funkcję obiektu A, ale także obiektów F, H
i N. Można to oczywiście wykonać. Ale żad-
na z implementacji nie jest tak elegancka
jak implementacja za pomocą zdarzeń. Po
pierwsze można w jednym z plików źródło-
wych programu utworzyć zmienne globalne,
w których będziemy przechowywać wskaź-
niki do stosownych obiektów. Ale zmienne
globalne są źle widziane. Duża liczba zmien-
nych globalnych świadczy o złym stylu pro-
gramowania. Zmiennych globalnych stara-
Listing 2.
Klasa zdarzeń Event
#ifndef INCLUDED_Event_h
#deine INCLUDED_Event_h
/*------- include iles:
------------------------------------------------*/
#include
<QEvent>
#include
<QString>
#include
<QVariant>
#include
<QVector>
/*------- class declaration:
-----------------------------------------------*/
class
Event
:
public
Qevent
(
1
)
{
// ******* TYPES *******
public
:
enum
{
OPEN_FILE_REQUEST
=
(
QEvent
::
User
+
1
)
,
(
2
)
NEW_FILE_REQUEST
,
SAVE_FILE_REQUEST
,
SAVE_FILE_AS_REQUEST
,
SAVE_ALL_FILES_REQUEST
,
PRINT_REQUEST
,
PROJECT_SELECTED_REQUEST
,
.....,
DBG_MEMORY_STOP
,
DBG_MEMORY_READ
,
DBG_MEMORY_DATA
,
}
;
public
:
Event
(
int
const
in_event_id
)
:
QEvent
(
static_cast
<
QEvent
::
Type
>(
in_event_id
))
{
d_data
.
reserve
(
0
);
}
Event
(
const
int
in_event_id
,
const
QVariant
in_val
)
:
QEvent
(
static_cast
<
QEvent
::
Type
>(
in_event_id
)
)
{
d_data
.
reserve
(
1
);
d_data
<<
in_val
;
}
Event
(
const
int
in_event_id
,
const
QVariant
in_val1
,
const
QVariant
in_val2
)
:
QEvent
(
static_cast
<
QEvent
::
Type
>(
in_event_id
)
)
{
d_data
.
reserve
(
2
);
d_data
<<
in_val1
<<
in_val2
;
}
Event
(
const
int
in_event_id
,
const
QVariant
in_val1
,
const
QVariant
in_val2
,
const
QVariant
in_val3
)
:
QEvent
(
static_cast
<
QEvent
::
Type
>(
in_event_id
)
)
{
d_data
.
reserve
(
3
);
d_data
<<
in_val1
<<
in_val2
<<
in_val3
;
}
Event
(
const
int
in_event_id
,
const
QVariant
in_val1
,
const
QVariant
in_val2
,
const
QVariant
in_val3
,
const
QVariant
in_val4
)
:
QEvent
(
static_cast
<
QEvent
::
Type
>(
in_event_id
)
)
{
d_data
.
reserve
(
4
);
d_data
<<
in_val1
<<
in_val2
<<
in_val3
<<
in_val4
;
}
public
:
QVector
<
QVariant
>
d_data
;
(
3
)
private
:
Event
(
const
Event
&
);
Event
&
operator
=(
const
Event
&
);
}
;
#
endif
// INCLUDED_Event_h
www.lpmagazine.org
45
dla programistów
Events Controller – kontroler zdarzeń
Listing 3.
Funkcja main programu tworząca obiekt kontrolera
Każde zdarzenie jest określonego typu.
Jest to liczba zawarta w typie wyliczenio-
wym
enum QEvent::Type
. Zawiera ona wie-
le predeiniowanych typów używanych we-
wnętrznie przez bibliotekę Qt. Ale, co ważne,
istnieje możliwość deiniowania własnych ty-
pów zdarzeń. Prywatne zdarzenia użytkow-
nika muszą zawierać się w zakresie od QE-
vent::User (wartość 1000) do
QEvent::MaxU-
ser
(wartość 65535).
Zdarzenia wysyła się z użyciem funkcji:
//*******************************************************************
// main
//*******************************************************************
int
main
(
int
in_argc
,
char
**
in_argv
)
{
....
EventsController
::
instance
();
// <<<<<< --------
....
}
// end of main
my się unikać. Więc to rozwiązanie odrzu-
camy. Druga metoda to utworzenie w każ-
dym obiekcie w ciągu (A,.....,M) funkcji po-
mocniczych wywołujących swój odpowied-
nik w następnym obiekcie w ciągu. Tak, aby
w końcu została wywołana stosowna funk-
cja obiektu M. Czyli mamy następującą sy-
tuację: aby wywołać jedną funkcję, wygene-
rujemy kilka, czy nawet dziesiątki, wywo-
łań pomocniczych. Można także zrobić tak,
aby obiekt A wywołał bezpośrednio funkcję
obiektu M. Ale, aby to było możliwe, trze-
ba jakoś przekazać do obiektu A wskaźnik
do obiektu M. Jeśli odrzucamy wersję ze
zmiennymi globalnymi, to pozostaje nam
ponownie ciąg funkcji przekazujących po-
między obiektami stosowny wskaźnik. Jest
to złe rozwiązanie. Nie dość, że tworzy-
my dużą liczbę funkcji pomocniczych, czy-
li wzrasta rozmiar programu, to jeszcze pro-
gram staje się coraz wolniejszy (każde wy-
wołanie kolejnej funkcji trwa przecież jakiś
czas). W Qt istnieje mechanizm signal/slot
lub signal/signal. Niestety to rozwiązanie
nie jest lepsze od poprzedniego. Znowu po-
jawia się konieczność kaskadowego łącze-
nia sygnałów i odpowiednich slotów w ca-
łym ciągu obiektów. Innym problemem mo-
że być sytuacja, gdy chcą się ze sobą komu-
nikować klasy, które nie są w tym samym
ciągu zależności. Trzeba tworzyć pomocni-
cze klasy do przekazywania informacji po-
między obiektami znajdującymi się w róż-
nych ciągach zależności. To, co powyżej opi-
sałem, to nie jedyny problem. Problemem,
i to dużym, staje się rozbudowa lub mody-
ikacja programu jako całości. Jeżeli zmieni-
my w jakiś sposób ciąg wzajemnych zależ-
ności obiektów bądź chcemy go rozbudo-
wać o nową funkcjonalność, trzeba pamię-
tać o konieczności zmodyikowania dzie-
siątek obiektów i funkcji. Dopisanie kolej-
nej klasy, z której chce korzystać obiekt A,
wymaga modyikacji wszystkich klas biorą-
cych udział w komunikacji. Usunięcie jednej
z klas wymaga usunięcia stosownych funk-
cji i ich wywołań we wszystkich klasach.
Możliwość zrobienia błędu czy zapomnienia
o czymś jest olbrzymia.
bool QCoreApplication::sendEvent(
QObject* receiver, QEvent* event )
oraz
Zdarzenia w Qt to podklasy
klasy QEvent
Jak już wspomniałem, systemy operacyjne
i graiczne (KDE, GNOME) wykorzystują
w swojej pracy tak zwane zdarzenia. Taką
możliwość udostępnia też swojemu użyt-
kownikowi biblioteka Qt. Z punktu widze-
nia programisty zdarzenie to obiekt, który
dziedziczy po abstrakcyjnej klasie QEvent.
W obiekcie mogą być zawarte informacje
(dane) dokładniej opisujące zdarzenie. Jeśli
będziemy wysyłać własne zdarzenia, to my
zdecydujemy, jakie informacje powinny się
w nim znaleźć. Zdarzenia mogą obsługiwać
wszystkie klasy pochodzące od QObject
(o tym należy pamiętać). Liczba wyspecja-
lizowanych zdarzeń w Qt jest naprawdę du-
ża:
QMouseEvent
,
QKeyEvent
,
QPaintEvent
,
QTimerEvent
itd. Zdarzenia w programie
napisanym w Qt obsługiwane są w tak zwa-
nej Pętli Obsługi Zdarzeń (Event-Loop).
Każdy interaktywny program musi posiadać
taką pętlę w obiekcie typu QApplication.
Także wygenerowane przez nas wątki (thre-
ad) mogą posiadać własne Pętle Obsługi
Zdarzeń. To ta właśnie pętla dba o rozsyła-
nie określonych zdarzeń do odpowiednich
obiektów.
void QCoreApplication::postEvent(
QObject* receiver, QEvent* event )
Różnica pomiędzy obiema funkcjami jest ta-
ka, że
sendEvent
wysyła zdarzenie do od-
biorcy bezpośrednio, ignorowane są inne zda-
rzenia znajdujące się w Pętli Obsługi Zda-
rzeń, a
postEvent
dokłada zdarzenie do wy-
słania na końcu kolejki pętli. Ta druga możli-
wość daje kolejce pewne możliwości optyma-
lizacji obsługi zdarzeń.
Funkcje służące do przechwytywania
zdarzeń to tak zwane handlery (Event-Han-
dler). Są to funkcje wirtualne, które można
(/należy) reimplementować we własnych
klasach.
Funkcje obsługujące zdarzenia powstają-
ce wewnątrz biblioteki Qt mają nazwy opiso-
we zaczynające się małą literą i kończące się
słowem Event, np. showEvent, keyPressE-
vent itd.
Proszę zwrócić uwagę na punkt 1. Aby
dialog, który jest klasą bazową dla DbgRegi-
sters, się faktycznie ukazał, zdarzenie należy
przesłać do klasy bazowej. A później może-
my się zastanowić, co chcemy zrobić w mo-
mencie zajścia zdarzenia. Jeżeli chcemy prze-
chwycić zdarzenia zanim zostaną wysłane do
Listing 4.
Deklaracja dziedziczenia
#include
<QApplication>
//*******************************************************************
// EventsController CONSTRUCTOR private
//*******************************************************************
EventsController
::
EventsController
()
:
QObject
()
{
setParent
(
qApp
);
}
// end of EventsControler
46
maj 2007
dla programistów
Events Controller – kontroler zdarzeń
wyspecjalizowanych handlerów, możemy
zaimplementować funkcje:
bool Qwidget::
event( QEvent* event ).
No i najważniejsze (w tym artykule oczy-
wiście). Do obsługi zdarzeń użytkownika,
czyli typów o numerze większym niż QEven-
tUser, służy specjalny wirtualny handler:
Listing 5.
Deklaracja klasy EventsController
#ifndef INCLUDED_EventsController_h
#deine INCLUDED_EventsController_h
/*------- include iles:
-------------------------------------------------------------------*/
#
include
"Event.h"
#include
<QObject>
#include
<QMutex>
#include
<QHash>
#include
<QSet>
void QObject::customEvent( QEvent*
event ).
Reimplementując tę funkcję w naszej kla-
sie, będziemy mogli obsługiwać swoje wła-
sne zdarzenia.
/*------- class declaration:
-------------------------------------------------------------------*/
class
EventsController
:
public
QObject
{
Q_OBJECT
Jak ja to zrobiłem
Szczegóły implementacji można znaleźć w pli-
kach Event.h, EventsController.cpp i Event-
sController.h mojego projektu o nazwie Co-
bras (www.beesoft.org).
Przede wszystkim utworzyłem własną
klasę zdarzeń o nazwie Event (bez Q):
// ******* CONSTRUCTION/DESTRUCTION *******
private
:
EventsController
();
~
EventsController
();
EventsController
(
const
EventsController
&
);
EventsController
&
operator
=(
const
EventsController
&
);
• Jak widać – moja klasa Event jest podkla-
są klasy QEvent . I tak być musi.
• Wszystkie zdarzenia w Qt muszą być po-
chodną tej właśnie klasy.
• Należy zauważyć, że typy moich zdarzeń
są zadeklarowane jako enum i pierwsze
zdarzenie ma numer QEventUser + 1.
Jest to po to, aby system wiedział, że to
moje zdarzenia i że to ja będę je obsługi-
wał poprzez customEvent.
• Teoretycznie zdarzenie może przekazy-
wać dowolną liczbę wartości QVariant
przechowywanych w wektorze QVector.
Na swoje potrzeby zaimplementowałem
konstruktory klasy pozwalające na prze-
kazanie od 0 do 4 wartości. Typ QVa-
riant zastosowałem, aby uniezależnić się
(częściowo przynajmniej) od typów prze-
kazywanych wartości.
// ******* TYPES *******
private
:
typedef
QHash
<
qint32
,
QSet
<
QObject
*
>
>
EventRegister
;
// ******* MEMBERS *******
private
:
static
EventsController
*
d_instance
;
EventRegister
d_items
;
QMutex
d_mutex
;
// ******* METHODS *******
public
:
static
EventsController
*
instance
();
bool
append
(
QObject
*
in_receiver
,
qint32
in_event_id
);
void
remove
(
QObject
*
in_receiver
);
void
send_event
(
qint32
in_event_id
);
void
send_event
(
qint32
in_event_id
,
QVariant
in_val
);
void
send_event
(
qint32
in_event_id
,
QVariant
in_val1
,
Qvariant
in_val2
);
void
send_event
(
qint32
in_event_id
,
QVariant
in_val1
,
QVariant
in_val2
,
QVariant
in_val3
);
void
send_event
(
qint32
in_event_id
,
QVariant
in_val1
,
QVariant
in_val2
,
QVariant
in_val3
,
QVariant
in_val4
);
I jeszcze uwaga. W zasadzie należy bezwzglę-
dnie unikać przekazywania do funkcji/kons-
truktorów parametrów przez wartość (tutaj:
obiektów typu Qvariant). Jednak w tym przy-
padku musiałem od tej reguły odstąpić. Je-
śli pokusimy się o przekazywanie parame-
trów przez wskaźniki lub referencję, możemy
mieć problemy, gdy obiekt wysyłający zdarze-
nie zostanie natychmiast zlikwidowany po je-
go wysłaniu. Może się okazać, że referencja lub
wskaźnik pokazuje na coś czego już nie ma.
Wiem, bo miałem taką właśnie sytuację
private
:
bool
exists
(
QObject
*
in_receiver
,
qint32
in_event_id
);
}
;
#
endif
// INCLUDED_EventsController_h
EventsController
Przede wszystkim klasa EventsController jest
zaimplementowana jako Singleton. Oznacza
48
maj 2007
dla programistów
Events Controller – kontroler zdarzeń
Listing 6.
Implementacja
funkcji MainWindow
Destruktor obiektu zostanie wywołany
automatycznie w momencie zakończenia pro-
gramu. Wynika to z faktu, że jako obiekt ma-
cierzysty obiektu kontrolera został w jego
konstruktorze wskazany obiekt qApp, czyli
sam program.
W celu zabezpieczenia danych prze-
chowywanych w zmiennej
EventRegister d_
items
, wprowadziłem mechanizm blokowa-
nia jednoczesnego dostępu za pomocą mu-
teksa.
MainWindow
::
MainWindow
()
:
QmainWindow
()
, ....
, ....
{
...
EventsController
::
instance
()->
append
(
this
,
Event
::
FILE_OPENED
);
EventsController
::
instance
()->
append
(
this
,
Event
::
FILE_CLOSED
);
EventsController
::
instance
()->
append
(
this
,
Event
::
ALL_FILES_CLOSED
);
EventsController
::
instance
()->
append
(
this
,
Event
::
FILE_RENAMED
);
Jak to działa
Zasada działania kontrolera jest banalnie
prosta. Dowolny obiekt dowolnego typu mo-
że wysłać dowolne zdarzenie w następu-
jący sposób:
.....
EventsController
::
instance
()->
append
(
this
,
Event
::
PROJECT_OPENED
);
EventsController
::
instance
()->
append
(
this
,
Event
::
PROJECT_CLOSED
);
EventsController
::
instance
()->
append
(
this
,
Event
::
UNDO_AVAILABLE
);
EventsController
::
instance
()->
append
(
this
,
Event
::
REDO_AVAILABLE
);
EventsController
::
instance
()->
append
(
this
,
Event
::
COPY_AVAILABLE
);
EventsController
::
instance
()->
append
(
this
,
Event
::
PROCESS_STARTED
);
EventsController
::
instance
()->
append
(
this
,
Event
::
PROCESS_BROKEN
);
EventsController
::
instance
()->
append
(
this
,
Event
::
DBG_STARTED
);
EventsController
::
instance
()->
append
(
this
,
Event
::
DBG_FINISHED
);
#include "EventsController.h"
.....
EventsController::instance()->send_
event( Event::DBG_CONTINUE_REQUEST );
.....
.....
I tu od razu widać potężny plus sterowania
programem za pomocą zdarzeń. Załóżmy hi-
potetycznie, że wiele obiektów, których nasz
obiekt nie zna oraz że obiekty, które jesz-
cze nie zostały zaimplementowane (!) są za-
interesowane tym zdarzeniem. W obiekcie,
w którym wysyłamy zdarzenie, zupełnie nas
to nie interesuje. Nie musimy znać odbior-
ców zdarzenia. Nie musimy znać ich wskaź-
ników ani nazw funkcji, które obsłużą zda-
rzeniem. Wysyłamy zdarzenie i już. Rozbu-
dowując program o kolejne klasy zaintere-
}
// end of MainWindow
to, że w programie można utworzyć jeden
i tylko jeden obiekt tego typu. Jest on tworzo-
ny tylko raz, przy starcie programu.
Ponieważ konstruktor klasy jest prywat-
ny, jedyną metodą dostępu do obiektu jest
wywołanie EventsController::instance(). Ta
funkcja zwraca wskaźnik do klasy. I to ta
funkcja tworzy obiekt, jeśli on jeszcze nie ist-
nieje. A jeśli obiekt istnieje, kolejne jej wywo-
łanie nie spowoduje ponownego utworze-
nia obiektu lecz zwróci wskaźnik do już ist-
niejącego.
Listing 7.
Implementacja Funkcji customEvent
//*****************************************************
// customEvent PRIVATE inherited
//*****************************************************
void
MainWindow
::
customEvent
(
QEvent
*
const
in_event
)
{
Event
*
const
event
=
dynamic_cast
<
Event
*
>
(
in_event
);
const
qint32
type
=
static_cast
<
qint32
>
(
in_event
->
type
()
);
case
Event
::
FILE_RENAMED
:
ile_renamed
(
event
);
break
;
case
Event
::
ACTIVE_DOCUMENT_CHANGED
:
active_document_changed
(
event
);
break
;
case
Event
::
CURSOR_POSITION_CHANGED
:
cursor_position_changed
(
event
);
break
;
case
Event
::
PROJECT_OPENED
:
project_opened
(
event
);
break
;
......
case
Event
::
DBG_DISABLE_RUN_REQUEST
:
active_debug_items
(
DBG_DISABLE_RUN
);
break
;
}
}
// end of customEvent
switch
(
type
)
{
case
Event
::
FILE_OPENED
:
ile_opened
(
event
);
break
;
case
Event
::
FILE_CLOSED
:
ile_closed
(
event
);
break
;
case
Event
::
ALL_FILES_CLOSED
:
all_iles_closed
();
break
;
www.lpmagazine.org
49
Plik z chomika:
SOLARIX33
Inne pliki z tego folderu:
2006.01_Koder plików w formacie OGG_[Programowanie].pdf
(722 KB)
2007.06_Piękno fraktali_[Programowanie].pdf
(1778 KB)
2008.11_GanttProject_[Programowanie].pdf
(1014 KB)
2007.04_USB Device Explorer_[Programowanie].pdf
(1134 KB)
2006.09_QT, PyQT – szybkie tworzenie baz danych_[Programowanie].pdf
(1319 KB)
Inne foldery tego chomika:
Administracja
Aktualnosci
Audio
Bazy Danych
Bezpieczenstwo
Zgłoś jeśli
naruszono regulamin