Zbirka nalog iz načrtovanja uporabniških vmesnikov Aleš Smrdel, Ciril Bohak, Miha Amon, Franc Jager 2017 Univerza v Ljubljani Fakulteta za računalništvo in informatiko Kataložni zapis o publikaciji (CIP) pripravili v Narodni in univerzitetni knjižnici v Ljubljani COBISS.SI-ID=292758784 ISBN 978-961-6209-95-3 (pdf) Copyright c 2017 Založba UL FRI. All rights reserved. Elektronska izdaja knjige je na voljo na URL: http://zalozba.fri.uni-lj.si/smrdel2017.pdf Recenzenta: viš. pred. dr. Borut Batagelj, viš. pred. dr. Alenka Kavčič Založnik: Založba UL FRI, Ljubljana Izdajatelj: UL Fakulteta za računalništvo in informatiko, Ljubljana 1. izdaja, 2017 Urednik: prof. dr. Franc Solina Kazalo 1 Programska arhitektura uporabniških vmesnikov 1 2 Uporabnost 27 3 Uporabniško usmerjeno načrtovanje 29 4 Sposobnosti človeka 33 5 Interakcije 45 6 Principi načrtovanja uporabniških vmesnikov 59 7 Navodila načrtovanja uporabniških vmesnikov 69 8 Načrtovanje in izbor ikon 111 9 Prototipi 115 10 Vrednotenje 119 11 Načrtovanje spletnih in mobilnih vmesnikov 131 Literatura 137 iii 1 Programska arhitektura uporabniških vmesnikov 1. Kaj je podoba (angl. “widget”) uporabniškega vmesnika? Kaj podobe omogo- čajo? Rešitev: Podoba je komponenta uporabniškega vmesnika, na primer meni, drsnik, vnosno polje, gumb oziroma drug element, s katerimi lahko gradimo uporabniške vmesnike. Podobe omogočajo visokonivojsko abstrakcijo za orodja za načrtovanje uporabniških vmesnikov. Orodja so tako zbirke podob, ki omogočajo enostavnejšo in lažjo gradnjo uporabniških vmesnikov. 2. Razložite razliko med AWT in Swing s stališča grafičnega prikaza gradnikov (podob). Rešitev: AWT je prilagodljivo in prenosljivo ogrodje razredov. Podprtim gradnikom so poiskani enakovredni sorodniki v operacijskih sistemih. Gra- dnike izriše gostujoči operacijski sistem. Aplikacije, ki so napisane v AWT, se razlikujejo med operacijskimi sistemi. Swing vsebuje razrede, izpeljane iz hierarhije AWT, kjer je osnovni gradnik JComponent. Gostujoči operacijski sistem prispeva pravokotno risalno po- vršino. Javanski navidezni stroj nariše lahke gradnike v stanju, v kakršnem so se znašli. Težki gradniki, kot so na primer JFrame, JWindow in JDialog, so gradniki, ki so povezani z izvornimi gradniki operacijskega sistema. Za njihov izris je zadolžen gostujoči operacijski sistem. 3. Naštejte nekaj lahkih vsebovalnikov okolja Swing, ki jih najdete tudi v generatorju vmesnikov NetBeans. Rešitev: Lahki vsebovalniki okolja Swing so: a) lahki generični vsebovalnik plošča (tudi “Panel” oziroma JPanel); b) vsebovalnik večstranska površina z možnostjo izbire (tudi “Tabbed Pane” oziroma JTabbedPane); 1 2 Poglavje 1 Programska arhitektura uporabniških vmesnikov c) vsebovalnik razcepljena površina (tudi “Split Pane” oziroma JSplitPane); d) drsna površina oziroma vsebovalnik lahkih komponent z drsniki (tudi “Scroll Pane” oziroma JScrollPane); e) orodna vrstica (tudi “Tool Bar” oziroma JToolBar); f) vsebovalnik za notranja okna (tudi “Desktop Pane” oziroma JDeskto- pPane); g) notranje okno oziroma lahki notranji vsebovalnik okvir (tudi “Internal Frame” oziroma JInternalFrame); h) vsebovalnik po globini (tudi “Layered Pane” oziroma JLayeredPane). 4. Naštejte nekaj načinov razvrščanja komponent v vsebovalnikih okolja AWT oziroma v vsebovalnikih okolja Swing. Rešitev: Nekateri načini razvrščanja so: a) programer nastavlja koordinate in dimenzije komponent (ali “Absolute Positioning” oziroma NULL); b) od leve proti desni, od zgoraj navzdol (ali tekoče razvrščanje oziroma FlowLayout); c) razvrščanje v mrežo (ali GridLayout); d) robno razvrščanje (ali BorderLayout); e) razvrščanje ena naenkrat (ali CardLayout); f) razvrščanje v mrežo različno velikih celic (ali GridBagLayout); g) razvrščanje po vertikali ali horizontali (ali BoxLayout); h) razvrščanje z vzmetmi (ali SpringLayout); i) skupinsko razvrščanje (ali “Free Design” oziroma GroupLayout). 5. Napišite aplikacijo (program), ki se bo lahko izvajala na oddaljenem raču- nalniku, okna pa se bodo prikazovala na lokalnem računalniku. Aplikacija naj vsebuje okno velikosti 400 ⇥ 300 pikslov. Aplikacija naj v zanki čaka na dogodke, ki jih proži uporabnik. V primeru pritiska tipke na tipkovnici naj se v terminalu izpiše tipka, ki je bila pritisnjena. Če pa je bila pritisnjena katera izmed tipk “b”, “c”, “r”, “z” ali “m”, pa naj se nastavi še barva ospredja okna (barva, s katero rišemo) na belo, črno, rdečo zeleno oziroma modro barvo. V primeru pritiska levega gumba na miški naj se v oknu izriše zapolnjen pravokotnik v barvi ospredja. Prvi pritisk naj samo določi položaj zgornjega levega oglišča pravokotnika, medtem ko drugi pritisk določi spodnje desno oglišče pravokotnika in povzroči izris. Ob pritisku srednjega gumba na miški naj se vsebina okna izbriše, ob pritisku desnega gumba na miški pa naj se okno (aplikacija) zapre. Napišite tudi ukaz za prevajanje te aplikacije. Poglavje 1 Programska arhitektura uporabniških vmesnikov 3 Rešitev: Pri tej rešitvi smo se odločili za uporabo okenskega sistema X11 in programskega jezika C. Aplikacijo bomo razdelili na nekaj funkcij. Najprej napišemo funkcijo (koda je prikazana v listingu 1.1), ki zgradi in prikaže okno, pri čemer predpostavimo, da so spremenljivke, ki jih uporabljamo v tej funkciji in niso najavljene (angl. “declared”) znotraj te funkcije, najavljene globalno. ⌥ ⌅ 1 void i n i c i a l i z i r a j X ( ) { 2 unsigned long crna , bela ; 3 4 p r i k a z = XOpenDisplay ( ( char ⇤) 0) ; 5 z a s l o n = DefaultScreen ( p r i k a z ) ; 6 barve = DefaultColormap ( prikaz , z a s l o n ) ; 7 crna = BlackPixel ( prikaz , z a s l o n ) ; 8 bela = WhitePixel ( prikaz , z a s l o n ) ; 9 10 okno=XCreateSimpleWindow ( prikaz , DefaultRootWindow ( p r i k a z ) , 0 , 0 , 400 , 300 , 5 , crna , bela ) ; 11 XSetStandardProperties ( prikaz , okno , " Naslov okna" , " Naslov minimiziranega okna" , None , NULL, 0 ,NULL) ; 12 XSelectInput ( prikaz , okno , ExposureMask | ButtonPressMask | KeyPressMask ) ; 13 14 kontekst=XCreateGC ( prikaz , okno , 0 , 0) ; 15 XSetBackground ( prikaz , kontekst , bela ) ; 16 XSetForeground ( prikaz , kontekst , crna ) ; 17 18 XClearWindow ( prikaz , okno ) ; 19 XMapRaised ( prikaz , okno ) ; 20 } ⌦ ⌃ ⇧ Listing 1.1 Inicializacija povezave s strežnikom X, kreiranje okna in prikaz okna. S kodo funkcije, ki je prikazana v listingu 1.1, najprej odpremo povezavo s strežnikom X, dobimo številko zaslona, privzeto barvno paleto ter vrednosti, ki določata črni in beli piksel (vrstice od 4 do 8). Nato kreiramo okno, mu določimo osnovne značilnosti in določimo, katere dogodke naj strežnik X javlja oziroma kateri dogodki so zanimivi (vrstice od 10 do 12). Nato določimo grafični kontekst in barvo ozadja ter ospredja (vrstice od 14 do 16). Na koncu okno še počistimo in ga prikažemo (vrstici 18 in 19). 4 Poglavje 1 Programska arhitektura uporabniških vmesnikov Sledi funkcija, s katero lahko zapremo okno, ki jo prikazuje koda v listingu 1.2. ⌥ ⌅ 1 void zapriX ( ) { 2 XFreeGC( prikaz , kontekst ) ; 3 XDestroyWindow ( prikaz , okno ) ; 4 XCloseDisplay ( p r i k a z ) ; 5 e x i t ( 1 ) ; 6 } ⌦ ⌃ ⇧ Listing 1.2 Razkrojitev grafičnega konteksta, uničenje okna in prekinitev povezave s strežnikom X. Pri zapiranju okna najprej razkrojimo grafični kontekst (vrstica 2), nato razkrojimo okno in vsa podokna (vrstica 3), na koncu pa še zapremo povezavo s strežnikom X, s čimer razkrojimo vse resurse, ki jih je odjemalec ustvaril na strežniku (vrstica 4), in končamo z izvajanjem (vrstica 5). Za tem napišemo še funkcijo, v kateri čakamo na dogodke, ki jih sproži uporabnik aplikacije (koda v listingu 1.3). Dogodki, ki jih aplikacija zazna, so dogodki ob pritisku na tipko, ob pritisku gumba na miški in ob prikazu okna. ⌥ ⌅ 1 void zankaDogodkov ( ) { 2 XEvent dogodek ; 3 KeySym k l j u c ; 4 char t e k s t [ 2 5 5 ] ; 5 i n t p r i t i s k =0; 6 i n t zacX , zacY ; 7 8 while ( 1 ) { 9 XNextEvent ( prikaz , &dogodek ) ; 10 i f ( dogodek . type==KeyPress && XLookupString(&dogodek . xkey , tekst ,255 ,& kl j u c , 0 ) ==1){ 11 i f ( t e k s t [0]== ’ b ’ ) { 12 barva . red = 65535; barva . green = 65535; barva . blue = 65535; 13 } 14 i f ( t e k s t [0]== ’ c ’ ) { 15 barva . red = 0 ; barva . green = 0 ; barva . blue = 0 ; 16 } 17 i f ( t e k s t [0]== ’ r ’ ) { 18 barva . red = 65535; barva . green = 0 ; barva . blue = 0 ; 19 } 20 i f ( t e k s t [0]== ’ z ’ ) { 21 barva . red = 0 ; barva . green = 65535; barva . blue = 0 ; Poglavje 1 Programska arhitektura uporabniških vmesnikov 5 22 } 23 i f ( t e k s t [0]== ’m’ ) { 24 barva . red = 0 ; barva . green = 0 ; barva . blue = 65000; 25 } 26 barva . f l a g s = DoRed | DoGreen | DoBlue ; 27 XAllocColor ( prikaz , barve , &barva ) ; 28 XSetForeground ( prikaz , kontekst , barva . p i x e l ) ; 29 p r i n t f ( " P r i t i s n i l i s t e tipko %c ! \ n" , t e k s t [ 0 ] ) ; 30 } 31 32 i f ( dogodek . type==ButtonPress ) { 33 i f ( dogodek . xbutton . button==1){ 34 i f ( p r i t i s k ==0){ 35 zacX=dogodek . xbutton . x ; 36 zacY=dogodek . xbutton . y ; 37 } 38 e l s e { 39 i f ( zacX 2 #i n c l u d e 3 #i n c l u d e 4 #i n c l u d e 5 6 Display ⇤ p r i k a z ; 7 i n t z a s l o n ; 8 Window okno ; 9 Colormap barve ; 10 XColor barva ; 11 GC k o n t e k s t ; 12 void i n i c i a l i z i r a j X ( ) ; 13 void zapriX ( ) ; 14 void zankaDogodkov ( ) ; 15 16 i n t main ( i n t argc , char ⇤ argv [ ] ) { 17 i n i c i a l i z i r a j X ( ) ; 18 zankaDogodkov ( ) ; 19 } ⌦ ⌃ ⇧ Listing 1.4 Glavni program. Program lahko prevedemo z GNU prevajalnikom za programski jezik C. Pri prevajanju aplikacije moramo dinamično povezati v naš program tudi knjižnico libX11. Tako lahko to aplikacijo prevedemo z ukazom (predpostavimo, da se celoten program nahaja v datoteki EnostavnoOknoX11.c): gcc -o EnostavnoOknoX11 EnostavnoOknoX11.c -lX11 Poglavje 1 Programska arhitektura uporabniških vmesnikov 7 Prevedeni program pa izvedemo z ukazom: ./EnostavnoOknoX11 Pri izvajanju programa pa moramo imeti nameščen okenski sistem X na strežniku in na odjemalcu. Operacijski sistemi Linux imajo ponavadi že nameščen okenski sistem X, pri Windows operacijskih sistemih pa je potrebno namestiti okolje cygwin za to, da lahko poganjamo aplikacije X11. Za opera- cijske sisteme Mac OS X pa obstaja projekt XQuartz, s pomočjo katerega je mogoče poganjati aplikacije X11. 6. Napišite aplikacijo (program) v programskem jeziku Java z uporabo knjižnice Swing. Aplikacija naj vsebuje okno velikosti 400 ⇥ 300 pikslov. Aplikacija naj čaka na dogodke, ki jih proži uporabnik. V primeru izbire tipke na tipkovnici naj se v terminalu izpiše tipka, ki jo je uporabnik pritisnil. V primeru, da je bila pritisnjena katera izmed tipk “b”, “c”, “r”, “z” ali “m”, pa naj se še nastavi barva ospredja okna (barva, s katero rišemo) na belo, črno, rdečo zeleno oziroma modro barvo. V primeru klika levega gumba na miški naj se v oknu izriše zapolnjen pravokotnik v barvi ospredja. Prvi klik naj samo določi položaj zgornjega levega oglišča pravokotnika, medtem ko drugi klik določi spodnje desno oglišče pravokotnika in povzroči izris. Ob kliku srednjega gumba na miški naj se vsebina okna izbriše, ob kliku desnega gumba na miški pa naj se okno (aplikacija) zapre. Rešitev: Naloga je podobna prejšnji nalogi, vendar je rešitev nekoliko krajša in lažja, ne omogoča pa izvajanja aplikacije na oddaljenem računalniku in prikaza na lokalnem računalniku. Najprej je potrebno narediti razred, ki bo razširjal razred JFrame in bo služil kot glavno okno aplikacije. Javanska koda, ki naredi potrebne korake, je prikazana v listingu 1.5. ⌥ ⌅ 1 import javax . swing . JFrame ; 2 import javax . swing . S w i n g U t i l i t i e s ; 3 import java . awt . BorderLayout ; 4 import java . awt . Dimension ; 5 6 p u b l i c c l a s s EnostavnoOkno extends JFrame{ 7 p r i v a t e EnostavnaPlosca p l o s c a ; 8 p u b l i c void i n i c i a l i z i r a j O k n o ( ) { 9 t h i s . s e t S i z e (new Dimension (400 , 300) ) ; 10 t h i s . s e t T i t l e ( " Enostavno okno" ) ; 8 Poglavje 1 Programska arhitektura uporabniških vmesnikov 11 t h i s . setLayout (new BorderLayout ( ) ) ; 12 p l o s c a = new EnostavnaPlosca ( ) ; 13 t h i s . add ( plosca , BorderLayout .CENTER) ; 14 } 15 16 p u b l i c s t a t i c void main ( S t r i n g [ ] args ) { 17 S w i n g U t i l i t i e s . invokeLater (new Runnable ( ) { 18 p u b l i c void run ( ) { 19 EnostavnoOkno okno = new EnostavnoOkno ( ) ; 20 okno . i n i c i a l i z i r a j O k n o ( ) ; 21 okno . s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame .EXIT_ON_CLOSE) ; 22 okno . s e t V i s i b l e ( true ) ; 23 } 24 }) ; 25 } 26 } ⌦ ⌃ ⇧ Listing 1.5 Okno aplikacije, ki razširja okvir (razred JFrame) skupaj z metodo main, ki ustvari in prikaže to okno. Ta razred vsebuje metodo, s katero oknu določimo velikost, naslov, razvršče- valnik, kreiramo komponento, v kateri se bo izvajal izris, in to komponento dodamo oknu (vrstice od 8 do 14). Okvir ima že privzeto nastavljeno robno razvrščanje (“BorderLayout”), tako da je potrebno samo še določiti predel, v katerega bomo dodali komponento. Ta predel je v našem primeru kar center. Razred pa vsebuje tudi metodo main (vrstice od 16 do 21), s katero kreiramo okno aplikacije. Ker večina komponent Swing ni nitno varnih, kreiramo okno aplikacije znotraj niti za dogodke, s katerimi kličemo metodo za inicializacijo komponent vmesnika in okno tudi prikažemo. Nato naredimo še razred za komponento, v kateri se bo izvajal izris. Ta komponenta bo tudi zaznavala dogodke, ki jih bo sprožil uporabnik. Koda za to komponento je prikazana v lisingu 1.6. ⌥ ⌅ 1 import javax . swing . JPanel ; 2 import java . awt . Color ; 3 import java . awt . Graphics ; 4 import java . awt . event . MouseEvent ; 5 import java . awt . event . MouseListener ; 6 import java . awt . event . KeyEvent ; 7 import java . awt . event . KeyListener ; 8 9 c l a s s EnostavnaPlosca extends JPanel implements KeyListener , MouseListener { Poglavje 1 Programska arhitektura uporabniških vmesnikov 9 10 p r i v a t e Color barva=Color . black ; 11 p r i v a t e i n t p r i t i s k =0; 12 p r i v a t e i n t zacX , zacY ; 13 14 p u b l i c EnostavnaPlosca ( ) { 15 t h i s . addKeyListener ( t h i s ) ; 16 t h i s . addMouseListener ( t h i s ) ; 17 t h i s . setBackground ( Color . white ) ; 18 t h i s . s e tFo c u s a b le ( true ) ; 19 } 20 21 p r i v a t e void r i s i ( i n t x , i n t y , i n t d , i n t s ) { 22 Graphics g=t h i s . getGraphics ( ) ; 23 g . s e t C o l o r ( barva ) ; 24 g . f i l l R e c t ( x , y , d , s ) ; 25 } 26 27 p u b l i c void keyTyped ( KeyEvent e ) { 28 switch ( e . getKeyChar ( ) ) { 29 case ’ b ’ : barva=Color . white ; break ; 30 case ’ c ’ : barva=Color . black ; break ; 31 case ’ r ’ : barva=Color . red ; break ; 32 case ’ z ’ : barva=Color . green ; break ; 33 case ’m’ : barva=Color . blue ; break ; 34 } 35 System . out . p r i n t l n ( " P r i t i s n i l i s t e tipko " + e . getKeyChar ( )+" ! " ) ; 36 } 37 38 p u b l i c void mouseClicked ( MouseEvent e ) { 39 i f ( e . getButton ( ) ==1){ 40 i f ( p r i t i s k ==0){ 41 zacX=e . getX ( ) ; 42 zacY=e . getY ( ) ; 43 } 44 i f ( p r i t i s k ==1){ 45 i f ( zacXbutton == 1) { 7 i f ( p r i t i s k ==0){ 8 x=event >x ; 9 y=event >y ; 10 } 11 e l s e { 12 cr = c a i r o _ c r e a t e ( povrsina ) ; 13 cairo_set_source_rgb ( cr , r , z , m) ; 14 c a i r o _ r e c t a n g l e ( cr , x , y , event >x x , event >y y ) ; 15 c a i r o _ f i l l ( cr ) ; 16 cairo_destroy ( cr ) ; 17 gtk_widget_queue_draw ( widget ) ; 18 } 19 p r i t i s k =( p r i t i s k +1)%2; 20 } 21 e l s e i f ( event >button==2){ 22 z b r i s i P o v r s i n o ( ) ; 23 gtk_widget_queue_draw ( widget ) ; 16 Poglavje 1 Programska arhitektura uporabniških vmesnikov 24 } 25 e l s e i f ( event >button==3){ 26 i f ( povrsina ) 27 cairo_surface_destroy ( povrsina ) ; 28 gtk_main_quit ( ) ; 29 } 30 return TRUE; 31 } 32 33 s t a t i c gboolean n a r i s i ( GtkWidget ⇤ widget , cairo_t ⇤ cr , g p o i n t e r data ) { 34 cairo_set_source_surface ( cr , povrsina , 0 , 0) ; 35 cairo_paint ( cr ) ; 36 return FALSE; 37 } ⌦ ⌃ ⇧ Listing 1.10 Obravnava dogodkov, povzročenih z miško, in risanje v risalno površino. Funkcija, ki jo prikazuje koda v listingu 1.10, obravnava dogodke z miške. V primeru, da se je zgodil pritisk levega gumba na miški (vrstice od 6 do 20), se v primeru prvega pritiska (vrednost števca pritiskov je enaka 0) shranijo koordinate tega pritiska (vrstice od 7 do 10), v primeru drugega pritiska (vrednost števca pritiskov je v tem primeru 1) pa se nastavi barva za risanje in nariše pravokotnik z začetnimi koordinatami prvega pritiska ter širino in višino, izračunano na podlagi razlike koordinat drugega in prvega pritiska (vrstice od 11 do 18). Pri tem lahko rišemo tudi z negativnimi vrednostmi za višino oziroma širino. V tem primeru se riše negativna stranica v obratno smer (v primeru negativne širine se bo pravokotnik risal od položaja prvega klika proti levi in ne proti desni). Po vsakem izrisu moramo izrisano tudi prikazati v oknu. To dosežemo tako, da sprožimo signal za risanje (“draw”), ki smo ga povezali z ustrezno odzivno funkcijo. Na ta način ročno sprožimo signal za ponoven izris okna ali le dela okna, ki ga je potrebno izrisati. Ob sprožitvi tega signala se bo klicala odzivna funkcija, ki je pridružena temu signalu. Nato še spremenimo stanje števca pritiskov levega miškinega gumba (vrstica 19). V primeru, da se je zgodil pritisk srednjega gumba na miški (vrstice od 21 do 24), se izbriše vsebina risalne površine, kjer se kliče funkcija, ki se uporablja tudi pri konfiguraciji risalne površine. V primeru, da se je zgodil pritisk desnega gumba na miški (vrstice od 25 do 29) se zbriše referenca na risalno površino in zaključi z zanko dogodkov. Druga funkcija (vrstice od 33 do 37) je odzivna funkcija, ki se kliče, kadar je sprožen signal za risanje. Pri knjižnici GTK+ verzije 3 je parameter, ki se prenese v odzivno funkcijo, grafični kontekst za risanje in ne več dogodek, kot je to bilo pri Poglavje 1 Programska arhitektura uporabniških vmesnikov 17 knjižnici GTK+ verzije 2 (npr. GdkEventExpose ob zahtevku za izris dela površine okna). Funkcija povzroči, da se ponovno nariše del vsebine okna. Iz risalne površine se ustvari vzorec (“pattern”), ki se določi kot izvor (“source”) v kontekstu cairo (vrstica 34), nato pa se ta vzorec nariše v trenutnem oknu (vrstica 35). Funkcijo zaključimo s tem, da vrnemo vrednost FALSE (vrstica 36), s čimer omogočimo nadaljnje obravnavanje dogodka. Sledi še funkcija, prikazana v listingu 1.11, ki obravnava dogodke s tipkovnice. ⌥ ⌅ 1 s t a t i c g i n t t i p k o v n i c a ( GtkWidget ⇤ widget , GdkEventKey ⇤ event ) { 2 i f ( s t r l e n ( event >s t r i n g )<=0) 3 return 1; 4 switch ( event >s t r i n g [ 0 ] ) { 5 case ’ b ’ : r=z=m=1; break ; 6 case ’ c ’ : r=z=m=0; break ; 7 case ’ r ’ : r =1; z=m=0; break ; 8 case ’ z ’ : r=m=0; z =1; break ; 9 case ’m’ : r=z =0;m=1; break ; 10 } 11 p r i n t f ( " P r i t i s n i l i s t e tipko : %c ! \ n" , event >s t r i n g [ 0 ] ) ; 12 return 0 ; 13 } ⌦ ⌃ ⇧ Listing 1.11 Obravnavanje dogodkov s tipkovnice. Koda, ki je prikazana v listingu 1.11, prebere, katera tipka na tipkovnici je bila pritisnjena. Najprej se preveri, če je bila pritisnjena katera izmed tipk, s katero določamo barvo, in glede na to ustrezno spremenimo vrednosti za rdečo, modro in zeleno barvno komponento (vrstice od 4 do 10). Na koncu še v terminal izpišemo pritisnjeno tipko (vrstica 11). Sledijo še vključevanje potrebnih zaglavnih datotek, deklaracija globalnih spremenljivk ter glavna (“main”) funkcija programa, kar prikazuje koda v listingu 1.12 . ⌥ ⌅ 1 #i n c l u d e 2 #i n c l u d e 3 #i n c l u d e 4 #i n c l u d e 5 6 GtkWidget ⇤okno ; 7 GtkWidget ⇤ r i s a l o ; 8 GtkWidget ⇤mreza ; 9 s t a t i c cai ro_s urf ace_ t ⇤ povrsina = NULL; 18 Poglavje 1 Programska arhitektura uporabniških vmesnikov 10 i n t r =0, z =0,m=0; 11 12 i n t main ( i n t argc , char ⇤ argv [ ] ) { 13 gtk_init (&argc , &argv ) ; 14 i n i c i a l i z a c i j a G T K ( ) ; 15 gtk_main ( ) ; 16 return 0 ; 17 } ⌦ ⌃ ⇧ Listing 1.12 Inicializacija grafične knjižnice, klic funkcije za izgradnjo okna in vstop v zanko dogodkov. V kodo listinga 1.12 najprej vključimo vse potrebne zaglavne datoteke (vrstice od 1 do 4), nato deklariramo globalne spremenljivke (vrstice od 6 do 10), katerim, kjer je potrebno, tudi določimo začetne vrednosti. Na koncu programa (z vsemi funkcijami) se nahaja še funkcija main (vrstice od 12 do 17), ki se kliče ob zagonu programa. V tej funkciji se najprej inicializira knjižnica GTK+ (vrstica 13). Inicializaciji knjižnice GTK+ sledi klic funkcije, ki kreira okno aplikacije in poveže signale ter dogodke z odzivnimi funkcijami (vrstica 14), nato pa vstopimo v zanko dogodkov (vrstica 15). Program se konča tako, da po izstopu iz zanke dogodkov vrnemo vrednost nič (vrstica 16), kar predstavlja normalno končanje programa. Celoten program lahko prevedemo z uporabo GNU prevajalnika za programski jezik C. Pri tem moramo paziti na vrstni red pisanja funkcij v datoteko s programom, saj prevajalnik zahteva, da so vse funkcije deklarirane, preden so vidne v kodi (funkcija inicializacijaGTK mora biti v datoteki deklarirana za vsemi odzivnimi funkcijami, ki jih v tej funkciji povezujemo s signali, torej za funkcijami: konfiguriraj, narisi, miska in tipkovnica), sicer pride do napake pri prevajanju. Celotno datoteko, za katero predpostavimo, da je v datoteki risiGTK.c, lahko prevedemo z naslednjim ukazom: gcc -o risiGTK risiGTK.c $(pkg-config --libs --cflags gtk+-3.0) S tem ukazom bomo izvorno kodo, ki se nahaja v datoteki risiGTK.c, prevedli v izvršljivi program risiGTK, ukaz pkg-config v zgornjem ukazu za prevaja- nje pa za knjižnico GTK+ verzije 3 nastavi vsa stikala, ki so potrebna za prevajanje programov. Preveden program izvedemo z ukazom: ./risiGTK Poglavje 1 Programska arhitektura uporabniških vmesnikov 19 Ekvivalenten uporabniški vmesnik lahko seveda ustvarimo tudi s programskim jezikom Python. Program, napisan v Pythonu, prikazuje koda v listingu 1.13. Opis kode je zelo podoben opisu programa, ki je napisan v programskem jeziku C, zato je izpuščen. ⌥ ⌅ 1 import g i ; 2 g i . r e q u i r e _ v e r s i o n ( ’ Gtk ’ , ’ 3 . 0 ’ ) 3 from g i . r e p o s i t o r y import Gtk ; 4 from g i . r e p o s i t o r y import Gdk ; 5 import c a i r o ; 6 po v rsi na=None ; 7 x=0; 8 y=0; 9 10 c l a s s MojeOkno ( Gtk . Window) : 11 p r i t i s k =0; 12 r=z=m=0; 13 def __init__ ( s e l f ) : 14 Gtk . Window . __init__( s e l f ) ; 15 s e l f . s e t _ t i t l e ( " A p l i k a c i j a " ) ; 16 s e l f . s e t _ d e f a u l t _ s i z e (400 , 300) ; 17 mreza = Gtk . Grid ( ) ; 18 s e l f . add ( mreza ) ; 19 r i s a l o = Gtk . DrawingArea ( ) ; 20 r i s a l o . set_can_focus ( True ) ; 21 r i s a l o . set_size_request (400 , 300) ; 22 mreza . attach ( r i s a l o , 0 , 0 , 1 , 1) ; 23 r i s a l o . set_events ( r i s a l o . get_events ( ) | Gdk . EventMask .BUTTON_PRESS_MASK | Gdk . EventMask . KEY_PRESS_MASK) ; 24 r i s a l o . connect ( " configure_event " , s e l f . k o n f i g u r i r a j ) ; 25 r i s a l o . connect ( "draw" , s e l f . n a r i s i ) ; 26 r i s a l o . connect ( " button_press_event " , s e l f . miska ) ; 27 r i s a l o . connect ( " key_press_event " , s e l f . t i p k o v n i c a ) ; 28 29 def z b r i s i P o v r s i n o ( s e l f ) : 30 g l o b a l povrsina ; 31 cr = c a i r o . Context ( povrsina ) 32 cr . set_source_rgb ( 1 , 1 , 1 ) 33 cr . paint ( ) 34 d e l cr 35 36 def k o n f i g u r i r a j ( s e l f , ⇤ args ) : 37 g l o b a l povrsina ; 20 Poglavje 1 Programska arhitektura uporabniških vmesnikov 38 i f povrsina i s not None : 39 d e l povrsina ; 40 povrsina = None ; 41 w = s e l f . get_allocated_width ( ) ; 42 h = s e l f . get_allocated_height ( ) ; 43 povrsina = s e l f . get_window ( ) . c r e a t e _ s i m i l a r _ s u r f a c e ( c a i r o .CONTENT_COLOR,w, h ) ; 44 s e l f . z b r i s i P o v r s i n o ( ) ; 45 46 def n a r i s i ( s e l f , ⇤ args ) : 47 g l o b a l povrsina ; 48 args [ 1 ] . set_source_surface ( povrsina , 0 , 0) ; 49 args [ 1 ] . paint ( ) 50 return False ; 51 52 def miska ( s e l f , ⇤ args ) : 53 g l o b a l x , y , povrsina ; 54 i f ( args [ 1 ] . button==1) : 55 i f ( s e l f . p r i t i s k ==0) : 56 x=args [ 1 ] . x ; 57 y=args [ 1 ] . y ; 58 e l s e : 59 cr = c a i r o . Context ( povrsina ) ; 60 cr . set_source_rgb ( s e l f . r , s e l f . z , s e l f .m) ; 61 cr . r e c t a n g l e ( x , y , args [ 1 ] . x x , args [ 1 ] . y y ) ; 62 cr . f i l l ( ) ; 63 d e l cr ; 64 s e l f . queue_draw ( ) ; 65 s e l f . p r i t i s k =( s e l f . p r i t i s k +1)%2; 66 e l i f ( args [ 1 ] . button==2) : 67 s e l f . z b r i s i P o v r s i n o ( ) ; 68 s e l f . queue_draw ( ) ; 69 e l i f ( args [ 1 ] . button==3) : 70 i f povrsina i s not None : 71 d e l povrsina ; 72 Gtk . main_quit ( ) ; 73 74 def t i p k o v n i c a ( s e l f , ⇤ args ) : 75 i f ( l e n ( args [ 1 ] . s t r i n g )<=0) : 76 return ; 77 i f args [ 1 ] . s t r i n g == "b" : 78 s e l f . r=s e l f . z=s e l f .m=1; 79 e l i f args [ 1 ] . s t r i n g == " c " : 80 s e l f . r=s e l f . z=s e l f .m=0; Poglavje 1 Programska arhitektura uporabniških vmesnikov 21 81 e l i f args [ 1 ] . s t r i n g == " r " : 82 s e l f . r =1; s e l f . z=s e l f .m=0; 83 e l i f args [ 1 ] . s t r i n g == " z " : 84 s e l f . r=s e l f .m=0; s e l f . z =1; 85 e l i f args [ 1 ] . s t r i n g == "m" : 86 s e l f . r=s e l f . z =0; s e l f .m=1; 87 p r i n t ( " P r i t i s n i l i s t e tipko " + args [ 1 ] . s t r i n g +" ! " ) ; 88 89 okno = MojeOkno ( ) ; 90 okno . connect ( " des t r oy " , Gtk . main_quit ) 91 okno . show_all ( ) ; 92 Gtk . main ( ) ⌦ ⌃ ⇧ Listing 1.13 Grafični uporabniški vmesnik, ki uporablja knjižnico GTK+, napisan v programskem jeziku Python. Pod predpostavko, da smo zgornjo kodo zapisali v datoteko z imenom ri- siGTK.py, lahko ta program izvedemo z ukazom: python risiGTK.py 9. Napišite spletno aplikacijo (program) za enostavno risanje z uporabo tehnolo- gij, ki se prikazujejo oziroma izvajajo v brskalniku. Aplikacija naj vsebuje risalno okno velikosti 400 ⇥ 300 pikslov. Aplikacija naj čaka na dogodke, ki jih proži uporabnik. V primeru izbire tipke na tipkovnici naj se na zaslonu izpiše natipkana tipka. V primeru, da je bila natipkana katera izmed tipk “b”, “c”, “r”, “z” ali “m”, pa naj se še nastavi barva ospredja okna na belo, črno, rdečo, zeleno oziroma modro barvo. V primeru klika levega gumba na miški naj se v oknu izriše zapolnjen pravokotnik v barvi ospredja. Prvi klik naj samo določi položaj zgornjega levega oglišča pravokotnika, medtem ko drugi klik določi spodnje desno oglišče pravokotnika in povzroči izris. Ob kliku srednjega gumba na miški naj se vsebina okna izbriše, ob kliku desnega gumba na miški pa naj se na zaslonu izpišejo navodila za končanje aplikacije. Rešitev: Pri izdelavi aplikacije bomo uporabili označevalni jezik HTML5, ki je namenjen določanju strukture dokumentov HTML, jezik za prekrivne sloge CSS, ki je namenjen določanju izgleda aplikacije, ter jezik JavaScript, ki se predvsem uporablja za dodajanje dinamičnosti spletnim dokumentom na strani odjemalca (brskalnika). V jeziku HTML bomo določili strukturo dokumenta HTML, z jezikom za prekrivne sloge bomo določili izgled elemen- tov, definiranih z jezikom HTML, z jezikom JavaScript pa bomo uporabniku omogočili risanje. 22 Poglavje 1 Programska arhitektura uporabniških vmesnikov Koda v listingu 1.14 prikazuje kodo, napisano v HTML in CSS, s pomočjo katere zgradimo strukturo spletne aplikacije. Ker se za prikaz spletnih aplikacij uporabljajo brskalniki, je za strukturo uporabniškega vmesnika potrebne relativno malo kode. ⌥ ⌅ 1 2 3 4 < t i t l e>Risanje l i k o v na platno 5 6 7 #mojePlatno { 8 border : 2px s o l i d #000000; 9 } 10 11 12 13 14 15 Brskalnik ne podpira HTML5 kanvasa 16 17
18 19 ⌦ ⌃ ⇧ Listing 1.14 Grafični uporabniški vmesnik, napisan v jeziku HTML in oblikovan z jezikom CSS. V kodi v listingu 1.14 najprej definiramo, da je to dokument HTML verzije 5 (vrstica 1). Nato sledi definicija strukture dokumenta HTML (vrstice 2 do 19). Struktura dokumenta HTML je deklarirana znotraj značke in je sestavljena iz dveh delov: glave dokumenta (vrstice 3 do 11) in telesa dokumenta (vrstice 13 do 18). Glava dokumenta je vsebovalnik za meta- podatke, kot so na primer naslov, nabor znakov, slogi za dokument, razne skripte, ter niso prikazani. V listingu 1.14 je tako definiran naslov dokumenta HTML oziroma strani (vrstica 4), ki se prikaže v zavihku, v katerem je stran prikazana, in v naslovni vrstici brskalnika, če je zavihek aktiven. Nato so defi- nirani uporabljen nabor znakov (vrstica 5) ter slogi za gradnike strani (vrstice 6 do 10), kjer smo samo definirali slog obrobe okoli risalne površine. Telo dokumenta HTML služi definiciji telesa oziroma strukture dokumenta HTML in hrani vsebino dokumenta HTML oziroma strani, kot so na primer besedilo, povezave, slike, table in drugi elementi HTML, vsebuje pa lahko tudi skripte. V telesu strani v listingu 1.14 je najprej definicija risalne površina Poglavje 1 Programska arhitektura uporabniških vmesnikov 23 (vrstice 14 do 16). Pri definiciji risalne površine smo dodali parametre za identifikacijo (id), s pomočjo katere bomo v kodi v jeziku JavaScript dostopali do tega elementa, višino (width) in širino (height) risalne površine ter za fokusiranje (tabindex), ki omogoča preusmeritev fokusa na risalno površino. V primeru, da brskalnik ne podpira risalne površine, se uporabniku namesto risalne površine prikaže obvestilo (vrstica 15). Na koncu (vrstica 17) sledi še definicija elementa za obvestila (
), ki je namenjen izpisu pritisnjenih tipk na tipkovnici ter prostoru za obvestilo uporabniku. Da bo stran odgovarjala na uporabniške akcije, je potrebno še registrirati rokovalnike dogodkov, kar prikazuje koda JavaScript v listingu 1.15. Ta koda se doda v telo dokumenta HTML (listing 1.14) takoj za definicijo risalne površine. ⌥ ⌅ 1 2 var cnv=document . getElementById ( "mojePlatno" ) ; 3 var ctx=cnv . getContext ( ’2d’ ) ; 4 var o b v e s t i l o=document . getElementById ( "obvestilo" ) ; 5 var barva="black" , p r i t i s k =0, zacX , zacY ; 6 cnv . addEventListener ( "mousedown" , gumbPritisnjen , f a l s e ) ; 7 cnv . addEventListener ( "keydown" , t i p k a P r i t i s n j e n a , f a l s e ) ; 8 cnv . addEventListener ( "contextmenu" , function ( event ) { event . preventDefault ( ) ; } , f a l s e ) ; 9 ⌦ ⌃ ⇧ Listing 1.15 Koda v jeziku JavaScript (znotraj značk script jezika HTML) za inicializacijo spremenljivk ter za registracijo rokovalnikov dogodkov. V kodi, prikazani v listingu 1.14, se najprej pridobi referenca na risalno povr- šino (vrstica 2). Ta referenca je potrebna zato, da lahko za risalno površino registriramo želene rokovalnike dogodkov. Nato se pridobi še referenca na grafični kontekst risalne površine (vrstica 3), v katerem se bo izvajalo risanje, ter na element za izpis pritisnjenih črk oziroma obvestila (vrstica 4). Nato se definirajo globalne spremenljivke (vrstica 5) ter rokovalnik dogodkov za pritisk miškinega gumba (vrstica 6) in za pritisk tipke na tipkovnici (vrstica 7). Na koncu moramo dodati še rokovalnik dogodkov za dogodek “kontekstni meni” (vrstica 8). To je dogodek, ki se na elementih zgodi ob kliku desnega miškinega gumba. Na risalni površini HTML () se ob tem dogodku privzeto prikaže kotekstni oziroma pojavni meni. V primeru te aplikacije je to nezaželeno obnašanje. Prikaz pojavnega menija ob pritisku desnega miškinega gumba preprečimo tako, da definiramo lasten rokovalnik dogodkov, v katerem samo določimo, da se privzeta akcija (prikaz pojavnega menija) 24 Poglavje 1 Programska arhitektura uporabniških vmesnikov ne izvede. Ob pritisku desnega miškinega gumba se tako najprej izvede ta rokovalnik dogodkov, prikaz pojavnega menija pa se dejansko prekliče. Sledi še definicija rokovalnikov dogodkov, ki je prikazana v listingu 1.16. Koda v tem listingu se doda na primer v glavo dokumenta HTML za značko