-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathkapitel_07.tex
1282 lines (1126 loc) · 57.6 KB
/
kapitel_07.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\ospchapter{WebKit}{WebKit}{chap:webkit}
\index{WebKit}%
Der Siegeszug von WebKit scheint unaufhaltsam: Praktisch alle großen
Computer"= und Internetfirmen (mit Ausnahme von Microsoft), kleine und
große Open-Source-Projekte sowie Hobby-Entwickler setzen mittlerweile
WebKit ein, um Webseiten darzustellen und Webanwendungen auszuführen.
Mit Chrome und Safari existieren zwei große Browser auf WebKit-Basis,
auf mobilen Geräten ist die Verbreitung mit Android, iOS, Symbian und
WebOS sogar noch größer. Und auch in KDE und Gnome wird WebKit zur
Grundlage aller Anwendungen, die mit HTML und Javascript zu tun haben.
Dabei begann das Projekt nicht sehr offen: Apple suchte nach einer
vernünftigen Grundlage für die HTML-Darstellung in MacOS und seinem
Webbrowser. Da der Mozilla-Code als schlechter organisiert und wartbar
als der von KHTML galt, nutzte Apple eben dieses KDE-Projekt als
Grundlage zur Entwicklung von WebKit. Der Quellcode wurde nach
Vorstellung des Produkts schließlich auch nach den Bedingungen der
KDE-Lizenzen veröffentlicht, wobei das Verhalten von Apple in der
Open-Source-Szene schnell auf Kritik stieß. Die Quellcode-Pakete waren
kaum mehr in KHTML zu integrieren und der Code damit für eine
Weiterentwicklung in der Community unbrauchbar. Apple nahm sich die
Kritik jedoch zu Herzen und öffnete 2005 den Entwicklungsprozess samt
Code Repository. Damit sprangen rasch andere Firmen wie Nokia und
Google auf den Zug auf, so dass WebKit derzeit wohl eines der von der
Industrie am meisten unterstützten Open-Source-Projekte ist.
Technisch besteht WebKit aus zwei Komponenten: WebCore zur Darstellung
von HTML und JavaScriptCore für die Interpretation und Kompilierung
von Javascript-Code. Basis von beidem waren, wie gesagt, KDE"=Projekte:
WebCore basiert auf KHTML, JavaScriptCore auf KJS. Seit Version 4.4
ist WebKit Teil des Qt-Frameworks, das ein WebKit-Widget und auch ein
WebKit-Element in QML zur Verfügung stellt. Beides lässt sich auf
einfache Weise in eigene Anwendungen integrieren und bietet so eine
bequeme Möglichkeit, Webseiten und Javascript in Qt-Anwendungen zu
integrieren und mit anderen Qt-Technologien interagieren zu lassen.
Auf diesem Weg hat WebKit schließlich auch wieder zurück zu KDE
gefunden, so dass der Browser und andere Komponenten des Projekts
jetzt direkt auf WebKit aufsetzen. Qt selbst ist derzeit auf dem Weg
der Modularisierung, wobei als eines der ersten Module die
WebKit-Komponente ausersehen wurde. So wird das Qt-WebKit in Zukunft
wohl unabhängiger von der Qt-Basis werden und damit schnellere
Entwicklungszyklen durchlaufen. Damit stellt WebKit einen der
wichtigsten Bausteine in Qt dar und bietet dem Anwendungsentwickler
die Möglichkeit zur Integration von Webtechnologien, wie sie auch in
anderen Frameworks und Betriebssystemen immer größeren Einfluss haben.
HTML5 und Javascript werden in Zukunft sicher die Basis für einen
großen Teil aller mobilen und Desktop-Anwendungen sein.
Dieses Kapitel stellt Ihnen anhand von drei Beispielen die Integration
von WebKit als Qt-Widget bzw. QML-Element in eine Qt-Anwendung vor.
Die Beispiele präsentieren Grundlagen der WebKit-Komponente und
stellen Ihnen alles Nötige zur Weiterentwicklung eigener Anwendungen
mit Webtechnologien bereit. Gleichzeitig werden auch Themen der
vorhergehenden Kapitel wieder aufgenommen und vertieft, etwa
die Benutzung des Qt Designers zur Erstellung von Oberflächen oder die
Benutzung bestimmter Qt-Klassen zur Implementierung der
Anwendungslogik (z.\,B. beim Speichern von Cookies).
\ospsection{Ein Webbrowser in Python}{Ein Webbrowser in Python}{sec:pythonwebbrowser}
Als erstes Beispielprojekt bauen wir aus dem WebKit-Widget einen
einfachen Webbrowser in Python. Der Browser soll erst einmal nicht
viel mehr können als vom Benutzer eine URL entgegennehmen, die
Webseite darstellen sowie eine Navigation per Links und Buttons
erlauben. Für die Navigation per Button sollen nur die drei
Basisfunktionen \ospmenu{Vorwärts} und \ospmenu{Zurück} für das
Blättern in den schon aufgerufenen Webseiten sowie \ospmenu{Neu laden}
zum erneuten Laden der gerade angezeigten Webseite implementiert
werden. Abbildung \ospfigref{fig:webkit1_app} zeigt schon einmal das
fertige Produkt, den laufenden Webbrowser unter Windows mit geladener
Webseite.
\ospfigure{0.9}{images/webkit1_app}{Webbrowser in Python}{fig:webkit1_app}
\ospsubsection{Erstellen der Oberfläche mit dem Qt Designer}{Erstellen der Oberfläche mit dem Qt Designer}{sec:pythonbrowserdesigner}
\index{Qt Designer}%
Wir nutzen für unser Beispiel den Qt Designer zur Erstellung der
Oberfläche, wie in Kapitel \ref{sec:qtdesigner} beschrieben. Die
Anwendung basiert also auf Qt-Widgets, nicht auf QML. Starten Sie
zunächst den Qt Designer aus dem PyQt- oder PySide-Verzeichnis Ihrer
Python-Installation. Als erstes Fenster zeigt der Designer wieder den
Wizard zur Erstellung eines neuen Formulars. Wählen Sie hier als
Template \ospmenu{Widget} (s. Abbildung
\ospfigref{fig:webkit1_newproject}).
\ospfigure{0.5}{images/webkit1_newproject}{Neues Designer-Projekt für Hautpfenster-Widget}{fig:webkit1_newproject}
Wir erstellen das Hauptfenster aus einem einfachen \ospcmd{QWdiget},
da wir keine Menü- und Statusleiste benötigen. Klicken Sie
anschließend auf den Button \ospmenu{Neu von Vorlage}.
Im nächsten Schritt müssen die einzelnen GUI-Elemente und das Layout
in das Hauptfenster-Widget eingefügt werden. Das Fenster enthält
insgesamt fünf Widgets: die drei Buttons, die Eingabezeile für die
URL sowie den WebKit-View zur Darstellung der Webseite. Angeordnet
sind die Elemente zunächst vertikal untereinander, nur die drei Buttons
sollen in einem horizontalen Layout mit einem Abstandshalter nach
rechts angeordnet werden. Abbildung \ospfigref{fig:webkit1_ui} zeigt das
Ergebnis im Qt Designer, nachdem alle Elemente hinzugefügt und
angeordnet wurden. Das Ergebnis wollen wir uns nun Schritt für Schritt
ansehen.
\ospfigure{1.0}{images/webkit1_ui}{Fertiger Webbrowser im Qt Designer}{fig:webkit1_ui}
\index{Qt Designer!Layout}%
\index{Qt Designer!Objekteigenschaften}%
\index{Qt Designer!Objektanzeige}%
Fügen Sie dem leeren Widget zunächst das horizontale Layout für die
Buttons hinzu, indem Sie aus der linken \ospmenu{Widgetbox} das
\ospmenu{Horizontal Layout} in den leeren Bereich des Hauptfensters
ziehen. Es erscheint dort ein roter Rahmen, der das Layout anzeigt. In
Abbildung \ospfigref{fig:webkit1_horizlayout} sehen Sie das Fenster
des Qt Designers, nachdem das Layout hinzugefügt wurde. Erst jetzt
können Sie dem Haupt-Widget das vertikale Layout zuweisen. Klicken Sie
dazu in der rechten Leiste mit der Aufschrift \ospmenu{Objektanzeige}
mit der rechten Maustaste auf das Objekt mit dem Namen \ospmenu{Form}
und wählen Sie aus dem Kontextmenü \ospmenu{Layout} und dann aus dem
Layout-Untermenü \ospmenu{Objekte senkrecht anordnen}. Das
Hauptfenster hat nun ein vertikales Layout, das das horizontale Layout
für die Buttons enthält. Wo wir gerade beim Hauptfenster sind: Sie
sollten in den \ospmenu{Eigenschaften} unten rechts im Designer gleich
die Werte für \ospmenu{objectName} und \ospmenu{windowTitle} auf etwas
Sinnvolleres setzen, beispielsweise auf \ospcmd{MainWindow} und
\ospcmd{Python Browser}. Der Objektname ist später in Python wichtig,
da der Name der Python-Klasse davon abgeleitet wird. Der
Fenstertitel wird nur für die Darstellung des Fensters verwendet.
\ospfigure{1.0}{images/webkit1_horizlayout}{Horizontales Layout im Hauptfenster}{fig:webkit1_horizlayout}
Im nächsten Schritt fügen Sie die einzelnen Elemente aus der linken
Leiste \ospmenu{Widgetbox} zum Hauptfenster hinzu. Achten Sie darauf,
dass Sie die Buttons und den horizontalen Spacer in das horizontale
Layout einfügen, nicht direkt in das Hauptfenster. Andernfalls
werden auch die Buttons vertikal angeordnet. Am einfachsten geht das,
indem Sie die Widgets \ospmenu{Push Button} sowie \ospmenu{Horizontal
Spacer} nicht in das Widget in der Mitte ziehen, sondern direkt auf
das \ospmenu{horizontalLayout} rechts in der \ospmenu{Objektanzeige}.
Diese Alternative besteht bei jedem der hinzuzufügenden Elemente:
entweder Sie lassen es direkt in das Widget fallen oder Sie fügen es
dem Objektbaum rechts in der Objektanzeige hinzu. Bei komplexeren
Layouts ist meist Letzteres die einfachere Methode. Zum Anordnen im
Layout können Sie die Widgets dann im grafischen Editor in der Mitte
hin und her schieben. Das Widget für die Eingabezeile heißt
\ospmenu{Line Edit}, für die Anzeige der Webseite fügen Sie einen
\ospmenu{QWebView} hinzu. Am Ende sollte es dann wie in Abbildung
\ospfigref{fig:webkit1_ui} aussehen.
Anschließend müssen die einzelnen Widgets mit sinnvollen Objektnamen
versehen werden. Die in unserem Beispiel verwendeten Namen sehen Sie
wiederum in Abbildung \ospfigref{fig:webkit1_ui} rechts oben in der
Objektanzeige: Die Eingabeleiste ist von der Klasse \ospcmd{QLineEdit}
abgeleitet und heißt \ospcmd{editAddress} usw. Klicken Sie doppelt
auf den jeweiligen Objektnamen und tragen Sie dann den neuen Namen
ein.
Darüber hinaus setzen wir für die Eingabezeile einen
Platzhaltertext. In den Abbildungen sehen Sie jeweils den String
\ospmenu{Hier die URL eingeben} in der Adresszeile. Dieser Text
verschwindet dann in der fertigen Anwendung, sobald der Benutzer auf
die Adresszeile klickt, um eine URL einzugeben. Wählen Sie dazu das
Objekt \ospcmd{editAddress} aus und geben Sie für die Eigenschaft
\ospcmd{placeholderText} den entsprechenden String ein. Speichern Sie
die UI-Datei dann unter dem Namen \ospfile{main.ui}.
Das war es auch schon. Die UI-Datei ist damit fertig, und wir können
uns an die Implementierung der Anwendungslogik machen: die Eingabe von
URLs, das Laden der Webseite und die Steuerung über die Buttons.
\ospsubsection{Der Browser in Python}{Der Browser in Python}{sec:pythonbrowsercode}
\index{uic}%
Dank Python, Qt und \ospcmd{QWebKit} beginnt nun der angenehme Teil:
Für den gesamten Browser brauchen wir am Ende nicht mehr als 30 Zeilen
handgeschriebenen Code. Zunächst muss aber aus der UI-Datei der
Python-Code erzeugt werden. Das erfolgt wieder über den Aufruf des
Skripts \ospfile{pyuic4} bzw. \ospfile{pyside-uic}. Starten Sie es
einfach mit der UI-Datei als Parameter und geben Sie das Ergebnis in
die Datei \ospfile{ui\_main.py} aus:
\begin{ospsimplelisting}
$ <bf>pyuic4 main.ui > ui_main.py</bf>
\end{ospsimplelisting}
Der Webbrowser selbst beginnt dann mit dem Einbinden der notwendigen
Module, neben den Standard-Python- und -Qt-Modulen dann eben auch das
gerade erzeugte Modul für die Benutzeroberfläche:
\begin{osplisting}{Python}{Einbinden der Module für den Browser}{code:webkit1import}
import sys, os
import re
from PyQt4 import QtCore, QtGui
from ui_main import Ui_MainWindow
\end{osplisting}
\index{GUI!.ui-Dateien verwenden}%
Wir werden in diesem Fall eine eigene Hauptfenster-Klasse von
\ospcmd{QWidget} ableiten und darin ein UI-Objekt aus der
automatisch erzeugten Klasse \ospcmd{Ui\_MainWindow} erzeugen, wie in
Kapitel \ref{sec:qtdesignerintegration} vorgestellt (die zweite
Variante, mit \ospcmd{self.ui} als UI-Objekt). Die Klasse nennen wir
\ospcmd{BrowserWindow} und speichern das erzeugte UI-Objekt in
\ospcmd{self.ui}, so dass wir innerhalb der Klasse über dieses
Attribut Zugriff auf alle GUI-Elemente haben. Die Deklaration und der
Konstruktor des Hauptfensters sehen dann folgendermaßen aus:
\begin{osplisting}{Python}{Start der Hauptfenster-Klasse}{code:webkit1mainwindow1}
class BrowserWindow(QtGui.QWidget):
"""Das Hauptfenster des Browsers."""
def __init__(self, *args):
QtGui.QMainWindow.__init__(self, *args)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.createConnects()
\end{osplisting}
\index{QLineEdit!returnPressed}%
\index{QWebView!urlChanged}%
\index{QWebView!back()}%
\index{QWebView!forward()}%
\index{QWebView!reload()}%
Der Konstruktor initialisiert also die GUI und ruft dann
\ospcmd{createConnects()} auf. In dieser Methode werden alle Signale
und Slots miteinander verbunden. In diesem Fall haben wir fünf Signale
zu verbinden: Klick auf einen der drei Buttons, Return nach Eingabe
der URL sowie den Klick auf einen Link im \ospcmd{QWebView}. Letzteres
ist notwendig, damit die Adresszeile auch immer die aktuelle URL
enthält. Der Web-View zeigt andernfalls zwar die neue Seite an, in
der Adresszeile würde aber weiterhin die alte URL sichtbar bleiben.
Für die Buttons können wir das Signal \ospcmd{clicked()} direkt mit
dem Aufruf von Methoden des \ospcmd{QWebView} verbinden. Er stellt mit
\ospcmd{back()}, \ospcmd{forward()} und \ospcmd{reload()} die drei
Funktionen bereit, die wir für die Buttons benötigen. Für die beiden
anderen Signale müssen wir noch eigene Slots implementieren. Die
Methode \ospcmd{createConnects()} sieht folgendermaßen aus:
\begin{osplisting}{Python}{Signale und Slots verbinden}{code:webkit1mainwindow2}
def createConnects(self):
self.ui.editAddress.returnPressed.connect(self.loadPage)
self.ui.webView.urlChanged.connect(self.updateUrl)
self.ui.buttonBack.clicked.connect(self.ui.webView.back)
self.ui.buttonForward.clicked.connect(self.ui.webView.forward)
self.ui.buttonReload.clicked.connect(self.ui.webView.reload)
\end{osplisting}
Das Signal für das Drücken von Return in der Eingabezeile heißt
also\osplinebreak{} \ospcmd{returnPressed()}. Wenn der Web-View eine
neue URL lädt, sendet er\osplinebreak{} \ospcmd{urlChanged()} mit der
neuen URL als Parameter. Diese beiden Signale sind nun mit unseren
eigenen Slots verbunden. Das Aktualisieren der Adresszeile besteht nur
aus dem Aufruf von \ospcmd{setText()} für das Objekt
\ospcmd{QLineEdit}, so dass der Slot \ospcmd{updateUrl()} recht kurz
ausfällt:
\begin{ospsimplelisting}
@QtCore.pyqtSlot(QtCore.QUrl)
def updateUrl(self, url):
self.ui.editAddress.setText(url.toString())
\end{ospsimplelisting}
Der Slot nimmt die neue URL entgegen und setzt sie als Inhalt der
Adresszeile. Beachten Sie, dass der Dekorator \ospcmd{pyqtSlot()}
jetzt einen Parameter \ospcmd{QtCore.QUrl} entgegennimmt. Bei Slots,
die einen Wert empfangen, ist die Angabe der Typen der Parameter immer
notwendig. Etwas aufwändiger ist die Antwort auf
\ospcmd{returnPressed()}. Die Klasse \ospcmd{QWebView} besitzt eine
Methode \ospcmd{load()} zum Laden einer URL. Sie nimmt aber nicht
einfach einen String mit der URL entgegen; vielmehr müssen wir aus dem
String ein Objekt der Klasse \ospcmd{QUrl} erzeugen. Der Konstruktor
der Klasse benötigt das Protokoll-Präfix, etwa \ospcmd{http://}, das
der Benutzer hier aber gar nicht eingegeben hat. Wir wollen also
zunächst prüfen, ob das Präfix vorhanden ist. Dann fügen wir es
gegebenenfalls zur eingegebenen Adresse hinzu und erzeugen das
URL-Objekt. Dieses wiederum übergeben wir an \ospcmd{load()}.
Insgesamt sieht die Implementierung von \ospcmd{loadPage()} dann so
aus:
\index{WebKit!Webseite laden}%
\index{QWebView!load()}%
\index{QUrl}%
\begin{osplisting}{Python}{Laden der eingegebenen URL}{code:webkit1mainwindow3}
@QtCore.pyqtSlot()
def loadPage(self):
stringUrl = unicode(self.ui.editAddress.text())
if not re.search(r"^http", stringUrl):
stringUrl = "http://" +stringUrl
url = QtCore.QUrl(stringUrl)
self.ui.webView.load(url)
self.ui.editAddress.clearFocus()
\end{osplisting}
Am Ende der Methode entfernen wir den Fokus von der Adresszeile, damit
der Cursor nicht mehr darin blinkt. Das Ende der Datei bildet wie
immer der Aufruf von \ospcmd{main()}:
\begin{ospsimplelisting}
if __name__ == "__main__":
main(sys.argv)
\end{ospsimplelisting}
Wenn Sie das Skript nun starten, haben Sie den fertigen Webbrowser vor
sich, wie in Abbildung \ospfigref{fig:webkit1_app} bereits
dargestellt. Sie können so mit Ihrem eigenen Browser durch das
Internet surfen. Persönliche Daten speichert der Browser zunächst
nicht permanent. Zwar werden Cookies gesetzt, allerdings nach
Schließen des Fensters wieder gelöscht. Das nächste Kapitel zeigt
Ihnen nun, wie Sie alle oder auch nur bestimmte Cookies beim Schließen
des Browser-Fensters speichern.
\ospsection{Cookies verwalten per Netzwerkmanager}{Cookies verwalten per Netzwerkmanager}{sec:pythoncookies}
\index{Cookies}%
\index{WebKit!Cookies verwalten}%
\index{Netzwerkmanager}%
\index{Netzwerkmanager!Cookies verwalten}%
\index{QNetworkAccessManager}%
Nach dem ersten Beispiel mit WebKit wollen wir in diesem Kapitel das
Thema vertiefen, und zwar bei der Behandlung von Cookies. WebKit
selbst ist dafür nicht zuständig, in Qt erledigt das der sogenannte
\emph{Netzwerkmanager}, ein Objekt der Klasse
\ospcmd{QNetworkAccessManager}. Er war auch schon in den bisherigen
Beispielen für die Verwaltung des Netzwerkverkehrs zuständig, wurde
aber für den Entwickler transparent vom Qt-Framework im Hintergrund
erzeugt und eingesetzt. Wir werden den Manager in diesem Kapitel
ansprechen, um zu bestimmten Webseiten gespeicherte Cookies auszulesen
und zu setzen. Die Fähigkeiten des Netzwerkmanagers gehen weit
darüber hinaus, so dass das hier vorgestellte Beispiel nur einen
Ausschnitt bieten kann. Weitere Informationen zum Netzwerkmanager
finden Sie in der Qt-Dokumentation.
\index{Google Tasks}%
Die Idee für dieses Beispiel entstand bei der täglichen Benutzung
eines Nokia N900 Mobiltelefons. Der Browser des Geräts ist zwar recht
brauchbar, allerdings hat er ein entscheidendes Manko: Unten rechts
ist immer ein Knopf der Browser-Oberfläche dargestellt, um den Browser
in den Vollbildmodus zu versetzen bzw. diesen wieder zu beenden.
Dieser Knopf überlagert immer einen Teil der Webseite. Falls dort
aber Links stehen, können diese nicht mehr angeklickt werden.
Besonders ärgerlich ist das bei der Benutzung des Dienstes Google
Tasks, da sich hier ein Hauptmenü unten rechts befindet. Auf dem N900
war dieses Hauptmenü einfach unerreichbar -- also musste ein eigener
Browser für Google Tasks her.
\index{QWebView}%
Ein vollständiger Webbrowser ist in diesem Fall aber gar nicht
notwendig, schließlich besteht der Dienst nur aus einer Webseite.
Diese ließe sich hervorragend in einem einfachen \ospcmd{QWebView}
darstellen, ohne weitere GUI-Elemente. Allerdings verlangt Google für
den Dienst ein Login samt Passwort, wobei bei erfolgreichem Login
einige Cookies übertragen werden, so dass der Browser beim nächsten
Zugriff auf den Dienst den Zugang sofort erhält, ohne dass sich der
Benutzer ein weiteres Mal einloggen muss. Die Idee ist nun, diese
Cookies beim Schließen des Web-Views zu speichern und beim nächsten
Öffnen wieder zu laden. So hätte man eine wirklich einfache Oberfläche
für Google Tasks, wobei man sich nur beim allerersten Besuch der
Webseite einloggen müsste. Genau das wollen wir hier in Python
implementieren.
\index{QtWebKit}%
\index{QtNetwork}%
Die Anwendung startet wie das Webbrowser-Beispiel auch mit dem Import
einer Reihe von Modulen, bevor die Haupt-Ereignisschleife in der
Funktion \ospcmd{main()} definiert wird:
\begin{osplisting}{Python}{Beginn des Google-Tasks-Browsers}{code:webkitcookies1}
import sys, os
import pickle, base64
from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork
def main(argv):
app = QtGui.QApplication(argv)
MainWindow = BrowserWindow()
MainWindow.show()
sys.exit(app.exec_())
\end{osplisting}
\index{Cookies!Cookie Jar}%
\index{QNetworkCookieJar}%
In diesem Fall werden nun allerdings zwei weitere Qt-Module
eingebunden, \ospcmd{QtWebKit} und \ospcmd{QtNetwork}. Das erste
benötigen wir, weil wir nun den Web-View nicht mehr im Designer zur
Oberfläche hinzufügen, sondern gleich in der Klasse
\ospcmd{BrowserWindow} erzeugen werden. Das zweite ermöglicht uns
Zugriff auf die Cookies. Die Klasse \ospcmd{BrowserWindow} ist unser
Hauptfenster und wird direkt von der Klasse \ospcmd{QWebView}
abgeleitet. Das Hauptfenster besteht demnach nur aus dem Web-View. Im
Konstruktor setzen wir zunächst Fenstergröße und -titel, dann
erzeugen wir den sogenannten \emph{Cookie Jar}:
\begin{osplisting}{Python}{Deklaration und Konstruktor des Hauptfensters}{code:webkitcookies2}
class BrowserWindow(QtWebKit.QWebView):
"""Das Hauptfenster des Browsers."""
def __init__(self, *args):
QtGui.QMainWindow.__init__(self, *args)
self.resize(400,500)
self.setWindowTitle("Google Tasks")
# Cookies laden
self.cookiejar = QtNetwork.QNetworkCookieJar(self)
\end{osplisting}
\osppagebreak
\index{QWebPage}%
\index{QWebView!page()}%
\index{QNetworkAccessManager!setCookieJar()}%
Damit ist der Code des Konstruktors aber noch nicht vollständig. Der
Cookie Jar enthält jetzt zwar eine Speichermöglichkeit für beliebig
viele Cookies, allerdings müssen wir diesen Speicher noch dem Web-View
zuweisen. Dazu brauchen wir Zugriff auf den Netzwerkmanager, der von
Qt automatisch für den Web-View zur Verwaltung des Netzwerkverkehrs
erzeugt wurde. Der Netzwerkmanager wird aber auch dem Web-View nicht
direkt zugeordnet. Stattdessen wird dem View ein Objekt der Klasse
\ospcmd{QWebPage} zugewiesen, das für die Darstellung der aktuellen
Seite zuständig ist. Dieses Objekt erlaubt nun den Zugriff auf den
Netzwerkmanager. Um den Cookie Jar für den Web-View verfügbar zu
machen, müssen Sie dem Konstruktor folgende Zeile hinzufügen:
\begin{ospsimplelisting}
self.page().networkAccessManager().setCookieJar(self.cookiejar)
\end{ospsimplelisting}
Über die Methode \ospcmd{page()} erhalten Sie das Objekt der Webseite;
es stellt wiederum per \ospcmd{networkAccessManager()} den aktuellen
Netzwerkmanager als Objekt der Klasse \ospcmd{QNetworkAccessManager}
zur Verfügung. Diesem können wir per \ospcmd{setCookieJar()} den
erzeugten Cookie Jar zuweisen. Der Web-View unterstützt damit das
Laden und Speichern von Cookies im Attribut \ospcmd{self.cookiejar}
unseres Hauptfensters. Über dieses Attribut haben wir nun jederzeit
Zugriff auf die aktuell gespeicherten Cookies.
\index{QWebView!load()}%
Die letzte Zeile des Konstruktors lädt nur noch die URL von
Google Tasks; wie im Webbrowser-Beispiel des vorherigen Kapitels
benutzen wir dazu die Methode \ospcmd{load()} des Web-Views:
\begin{ospsimplelisting}
self.load(QtCore.QUrl("https://mail.google.com/tasks/ig"))
\end{ospsimplelisting}
Damit ist die Google-Tasks-Oberfläche geladen. Wenn Sie die Anwendung
jetzt starten, sieht der Benutzer zunächst den Login von Google.
Melden Sie sich mit Ihrem Google Account an, erscheint die
Aufgabenliste, die der Benutzer beliebig editieren kann (s. Abbildung
\ospfigref{fig:webkit2_app}). Alle Eingaben werden bei Google
gespeichert, der Qt-Web-View unterstützt die volle Funktionalität der
Webanwendung. Wenn der Benutzer das Fenster schließt und die
Anwendung erneut startet, muss er allerdings seine Login-Daten erneut
eingeben.
\index{QSettings}%
\index{Anwendung!Einstellungen speichern}%
\index{Events!Schließen des Fensters}%
\index{QWidget!closeEvent()}%
Im nächsten Schritt sollen die Cookies von Google Tasks beim Schließen
des Fensters gespeichert werden. Alle dafür notwendigen Techniken
wurden schon in Kapitel \ref{sec:spezielleeventhandler} vorgestellt:
Wir nutzen die Klasse \ospcmd{QSettings} zum Speichern von
Einstellungen und überschreiben den Event-Handler
\ospcmd{closeEvent()} unseres Hauptfensters, um die Daten genau beim
Schließen speichern zu können. Der fehlende Teil ist dann nur der
Zugriff auf die eigentlichen Cookie-Daten sowie die Umwandlung dieser
Daten in ein zum Speichern geeignetes Format.
\osppagebreak
\ospfigure{0.5}{images/webkit2_app}{Hautpfenster des Google-Tasks-Browsers}{fig:webkit2_app}
Cookies sind eine spezielle Eigenschaft des HTTP-Protokolls und
bestehen jeweils aus einem Namen und einem Wert. Außerdem sind sie
jeweils einer URL zugeordnet. Der Server überträgt diese drei Angaben
an den Client, der entscheidet, ob er das Cookie annimmt oder
ignoriert. Beim nächsten Zugriff auf die URL oder eine Unterseite der
URL kann der Client das Cookie wieder an den Server senden, so dass
der Server den Client über mehrere Zugriffe hinweg identifiziert. In
unserem Fall ist die URL immer dieselbe, und wir müssen lediglich alle
Cookies für diese URL speichern. Alle anderen Cookies können wir
getrost ignorieren.
\index{QNetworkCookieJar!cookiesForUrl()}%
\index{QUrl}%
\index{pickle}%
\index{Objektserialisierung}%
\index{Base64-Encoding}%
Zu diesem Zweck stellt der Cookie Jar eine Methode
\ospcmd{cookiesForUrl()} bereit, der wir ein Objekt der Klasse
\ospcmd{QUrl} mit der URL von Google Tasks übergeben. Zum Speichern
der Cookies benutzen wir hier das Standard-Python-Modul
\ospcmd{pickle}, das Python-Objekte in Strings serialisiert und aus
diesen Strings auch wieder das Objekt erzeugt. Leider gibt es dabei
zwei Probleme: Das Ganze funktioniert nicht mit Qt-Objekten, da diese
zusätzliche Laufzeit-Informationen speichern, die beim Serialisieren
in diesem Fall verlorengehen; außerdem erzeugt \ospcmd{pickle}
ASCII-Strings, die beim Speichern über \ospcmd{QSettings} zumindest
unter Windows Probleme bereiten, da sie auch Zeichen für \dqo{}neue
Zeile\dqc{} und Ähnliches enthalten können. Wir müssen also zwei
zusätzliche Schritte durchführen.
Zuerst wandeln wir die Cookies aus Objekten der Klasse
\ospcmd{QNetworkCookie} in einfache Python-Tupel aus Name und Wert
um und speichern diese statt in einem Cookie Jar in einer
Python-Liste. Diese Liste serialisieren wir zwar dann per
\ospcmd{pickle}, kodieren den String aber anschließend noch per
Python-Modul \ospcmd{base64}, so dass nur die druckbaren Zeichen aus
dem ASCII-Inventar enthalten sind (Base64-Encoding). Der folgende
Event-Handler führt beide Schritte durch, wobei die Umwandlung in eine
Python-Liste in eine private Methode
\ospcmd{\_cookieListFromCookies()} ausgegliedert ist:
\begin{osplisting}{Python}{Event-Handler für das Schließen des Hauptfensters}{code:webkitcookies3}
def closeEvent(self, event):
cookies = self.cookiejar.cookiesForUrl(
QtCore.QUrl("https://mail.google.com/tasks/ig")
)
cookie_list = self._cookieListFromCookies(cookies)
settings = QtCore.QSettings("dasskript.com", "GoogleTasks")
settings.setValue("cookies", QtCore.QString(
base64.b64encode(pickle.dumps(cookie_list))));
event.accept()
\end{osplisting}
Zuerst werden also die Cookies per \ospcmd{cookiesForUrl()}
ausgelesen, dann in eine Liste aus Tupeln gewandelt. Die dazu
notwendige private Methode soll gleich noch vorgestellt werden. Die
Liste wird dann per \ospcmd{pickle.dumps()} und
\ospcmd{base64.b64encode()} in einen ASCII-String umgewandelt und über
das \ospcmd{QSetting}-System als Einstellung für diese Anwendung
gespeichert. Zur Erinnerung: \ospcmd{QSetting} speichert
Anwendungsdaten, indem es jeweils eine Organisation und den
Anwendungsnamen als ID entgegennimmt. Unter dieser ID werden dann die
Daten permanent abgelegt und können später wieder ausgelesen werden.
Unter jedem Betriebssystem wird dazu der spezifische Speicher
verwendet, beispielsweise Dateien in \ospfile{.config} unter
Unix-Systemen, die Registry unter Windows. Zuletzt ruft der
Event-Handler die \ospcmd{accept()}-Methode des Ereignisses auf,
um das Ereignis als akzeptiert zu kennzeichnen und das Fenster zu
schließen.
\index{QNetworkCookie}%
\index{QNetworkCookie!name()}%
\index{QNetworkCookie!value()}%
Der Aufruf der Methode \ospcmd{cookiesForUrl()} liefert im
Ereignis-Handler eine Liste von \ospcmd{QNetworkCookie}-Objekten
zurück. In der Methode \ospcmd{\_cookie\-ListFromCookies()} wollen wir
die Cookies in eine Python-Liste ablegen, und zwar in ein Tupel aus
Name und Wert pro Cookie. \ospcmd{QNetworkCookie} stellt dazu zwei
Methoden \ospcmd{name()} und \ospcmd{value()} bereit, die wir für
jedes Cookie aufrufen. Der komplette Code der privaten Methode
sieht dann folgendermaßen aus:
\begin{osplisting}{Python}{Umwandlung der Cookies in Tupel}{code:webkitcookies4}
def _cookieListFromCookies(self, cookies):
cookie_list = []
for cookie in cookies:
cookie_list.append((cookie.name(), cookie.value()))
return cookie_list
\end{osplisting}
Der umgekehrte Vorgang, die Erzeugung einer Liste von
\ospcmd{QNetworkCookie}-Objekten, sieht dann so aus:
\osppagebreak
\begin{osplisting}{Python}{Umwandlung von Tupeln in Cookies}{code:webkitcookies5}
def _cookiesFromCookieList(self, cookie_list):
cookies = []
for (name, value) in cookie_list:
c = QtNetwork.QNetworkCookie(name, value)
cookies.append(c)
return cookies
\end{osplisting}
Der Konstruktor der Klasse \ospcmd{QNetworkCookie} nimmt zur Erzeugung
eines Cookies einfach dessen Namen und Wert entgegen. Nun müssen
wir beim Anzeigen des Hauptfensters diese Tupel natürlich wieder aus
\ospcmd{QSetting} laden. Dazu rufen wir die
\ospcmd{value()}-Methode von \ospcmd{QSettings} auf, dekodieren den
Cookie-String per \ospcmd{base64.b64decode()} und machen die
Serialisierung mit \ospcmd{pickel.loads()} rückgängig. Die daraus
erzeugte Python-Liste können wir nun an die gerade implementierte
private Methode \ospcmd{\_cookies\-FromCookieList} übergeben. Ergänzen
Sie dazu die beiden Zeilen im Konstruktor unter dem Kommentar
\ospcmd{Cookies laden} um folgenden Code:
\index{QNetworkCookieJar!setCookiesForUrl()}%
\index{QNetworkAccessManager!setCookieJar()}%
\begin{osplisting}{Python}{Laden der Coookies im Konstruktor}{code:webkitcookies6}
# Cookies laden
self.cookiejar = QtNetwork.QNetworkCookieJar(self)
settings = QtCore.QSettings("dasskript.com", "GoogleTasks")
cookie_list = settings.value("cookies", "").toString()
if cookie_list != "":
cookie_list = pickle.loads(base64.b64decode(cookie_list))
cookies = self._cookiesFromCookieList(cookie_list)
self.cookiejar.setCookiesFromUrl(
cookies,
QtCore.QUrl("https://mail.google.com/tasks/ig")
)
self.page().networkAccessManager().setCookieJar(self.cookiejar)
\end{osplisting}
Die erste und letzte Zeile entsprechen dem oben angegebenen Code.
Dazwischen wird nun aber die Cookie-Liste aus den
Anwendungseinstellungen geladen. Falls der Wert der Einstellung
\ospcmd{cookies} nicht leer ist, wird eben eine Liste von
Cookie-Objekten aus dem geladenen String erzeugt und per
\ospcmd{setCookiesFromUrl()} in den Cookie Jar geladen.
Die Webanwendung Google Tasks erhält damit automatisch alle beim
letzten Schließen des Fensters gespeicherten Cookies. Wenn sich der
Benutzer nun einmal eingeloggt hat, wird diese Information beim
nächsten Öffnen der Anwendung an die Webanwendung übermittelt und der
Benutzer bekommt sofort die Aufgabenliste angezeigt.
Dieses Beispiel lässt sich natürlich an beliebige andere
Webanwendungen anpassen. Die Bedienung des Dienstes ohne vollständige
Browser"=Oberfläche ist meist einfacher und intuitiver. Außerdem lässt
sich die Fenstergröße optimal an den darzustellenden Inhalt anpassen.
Mit Qt und seiner WebKit-Komponente lassen sich auf diese einfache
Weise Desktop-Anwendungen aus Webseiten erstellen.
\ospsection{WebKit in QML}{WebKit in QML}{sec:pythonwebkitqml}
\index{WebKit!QML}%
\index{QML!WebKit}%
\index{QML!WebView}%
Dank dem schon in Kapitel \ref{sec:pureqmlapplications} vorgestellten
\ospcmd{WebView}-Element lässt sich WebKit nicht nur als Qt-Widget
einsetzen, sondern auch in eine QML-Oberfläche integrieren. In den
bisherigen QML-Beispielen wurde WebKit eingesetzt, um eine Webseite
aus dem Internet zu laden und anzuzeigen. Dazu war nicht viel mehr
notwendig, als die Eigenschaft \ospcmd{url} des Web-Views zu setzen.
Alles andere erledigte Qt Quick für uns und griff dazu im Hintergrund
auf die Qt-WebKit-Komponente zurück. Das Beispiel in diesem Kapitel
wird darüber hinaus zeigen, wie Sie eine komplette, lokale
Webanwendung in einen QML-View einbetten. Vor allem das Zusammenspiel
zwischen HTML-Seite, QML-Elementen und Javascript auf beiden Seiten
soll dabei im Vordergrund stehen.
\index{QML!Javascript}%
\index{Processing.js}%
\index{Javascript}%
Durch die Javascript-Unterstützung in QML und WebKit kann der
Qt"=Entwickler auf eine Reihe von Javascript-Bibliotheken
zurückgreifen. Diese lassen sich zum einen per HTML-Seite in WebKit
einbinden, wobei die HTML-Seite den Javascript-Code lädt. Außerdem
bietet QML die Möglichkeit zum direkten Einbinden externer
Javascript-Dateien. Hier nutzen wir die erste Methode: die Einbettung
einer HTML-Seite samt Javascript in dem QML-Web-View und die
Möglichkeit zur Kommunikation zwischen dem QML- und dem HTML-Teil der
Anwendung. Als Beispiel werden wir einen
Processing"=Interpreter\ospfootnote{fn:processing}{Mehr zur
Programmiersprache Processing unter \ospurl{http://processing.org/}}
implementieren: Die QML-GUI ermöglicht dem Benutzer das Editieren des
Processing-Quellcodes, die HTML-Seite führt diesen Code dann aus.
Aufgabe ist, den eingegebenen Quellcode an die Javascript-Umgebung der
HTML-Seite weiterzugeben. Wir greifen dazu auf die
Java\-script"=Bibliothek
processing.js\ospfootnote{fn:processingjs}{\ospurl{http://processingjs.org/}
Auf der Webseite finden Sie auch Beispiele, die sich direkt in
unserer Anwendung ausführen lassen.} zurück, ein in Javascript
entwickelter Processing"=Interpreter. Processing wird häufig zur
Grafikprogrammierung und Visualisierung eingesetzt und bietet einen
einfachen Zugang selbst für ungeübte Entwickler. Darum kann unsere
Entwicklungs-GUI auch recht einfach ausfallen: Der Quelltext muss
eingegeben und gestartet werden können, mehr ist gar nicht vorgesehen.
Intern führt processing.js den Code in einem HTML5-Canvas aus, wobei
die Grafikausgabe per Javascript erfolgt. Mit all dem brauchen wir uns
zunächst aber gar nicht weiter auseinanderzusetzen, die Bibliothek und
WebKit stellen alles bereit. Wir konzentrieren uns auf die GUI und
das Senden des Processing-Codes an die WebKit-Komponente.
Abbildung \ospfigref{fig:webkit3_qml_app} zeigt die fertige Anwendung.
Das hier vorgestellte Beispiel ist in etwas erweiterter Form auch als
Github-Projekt
verfügbar.\ospfootnote{fn:Process}{\ospurl{https://github.com/pbouda/Process-}}
Dort haben Sie Zugriff auf den gesamten Quelltext der Anwendung.
Jeder Leser ist herzlich eingeladen, Ideen und Code zum Projekt
beizutragen.
\osppagebreak
\ospfigure{0.7}{images/webkit3_qml_app}{Hauptfenster des Processing-Interpreters}{fig:webkit3_qml_app}
\ospsubsection{Hauptanwendung in Python}{Hauptanwendung in Python}{sec:webkit3python}
Die Hauptanwendung in Python kann wieder minimal gehalten werden, da
alles Weitere in QML und HTML implementiert wird. Es soll an dieser
Stelle jedoch eine Erweiterung eingeführt werden, und zwar starten wir
den deklarativen View mit OpenGL-Unterstützung, falls vorhanden. Damit
versuchen wir die Grafikdarstellung zu beschleunigen. Manchmal bringt
der Einsatz von OpenGL aber keinen Vorteil, da auch die Grafikkarte
und deren Treiber OpenGL unterstützen müssen. Vor allem unter Linux
kommt es hier oft zu Problemen. Dann greift OpenGL auf eine
Software-Implementierung zurück, die sogar langsamer ist als die
native Implementierung in Qt Quick. Sie können auf Ihrem System beide
Varianten einmal ausprobieren und die Geschwindigkeit bei der
Darstellung vergleichen. Oft greift die OpenGL-Unterstützung auch erst
im Vollbild-Modus, so dass wir unserer Python-Anwendung einen
Parameter zum Start im Vollbildmodus hinzufügen wollen. Erst bei
Angabe dieses Parameters versucht dann die Anwendung die
Qt-Quick-Umgebung zum OpenGL-Rendering zu überreden. Der Python-Code
sieht folgendermaßen aus:
\index{Anwendung!Template}%
\index{optparse}%
\index{Anwendung!Kommandozeilenargumente}%
\index{Anwendung!Vollbildmodus}%
\index{OpenGL}%
\index{QtOpenGL}%
\index{QGLWidget}%
\index{QML!OpenGL-Beschleunigung}%
\index{QAbstractScrollArea!setViewport()}%
\index{QWidget!showFullScreen()}%
\begin{osplisting}{Python}{Hauptanwendung zum Start des Processing-Interpreters}{code:webkit3_python}
import os, sys, optparse
from PySide import QtCore, QtGui, QtDeclarative
def main(argv):
parser = optparse.OptionParser()
parser.add_option("-f", "--fullscreen", dest="fullscreen", \
help="Anwendung im Vollbildmodus starten.", \
action="store_true", default=False)
(options, args) = parser.parse_args()
app = QtGui.QApplication(argv)
view = QtDeclarative.QDeclarativeView()
view.setSource(QtCore.QUrl("main.qml"))
view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)
if options.fullscreen:
from PySide import QtOpenGL
glw = QtOpenGL.QGLWidget()
view.setViewport(glw)
view.showFullScreen()
else:
view.show()
app.exec_()
if __name__ == "__main__":
main(sys.argv)
\end{osplisting}
Das Skript benutzt \ospcmd{optparse.OptionParser()} zum Auslesen der
Kommandozeilenargumente. Der Parameter für den Vollbildmodus heißt in
diesem Fall einfach \ospcmd{-f} oder \ospcmd{-{}-fullscreen}. Als
Standard ist der Vollbildmodus ausgeschaltet.
Als Hauptfenster verwenden wir das QML-Widget
\ospcmd{QDeclarativeView}, da wir sonst keine weiteren Qt-Widgets in
der Oberfläche benötigen. Streng genommen bräuchten wir für die
Anwendung gar keinen Python-Code, sie ließe sich mit dem QML-Viewer
ausführen. Wenn Sie eine solche Anwendung aber später per
Installationspaket vertreiben wollen, wie in Kapitel
\ref{fig:webkit3_qml_app} beschrieben, geht das eben am einfachsten
mit einer kleinen Python-Rahmen-Anwendung. Den Resize Mode setzt das
Skript gleich auf \ospcmd{Size\-RootObjectToView}, da der Benutzer die
Fenstergröße ändern können soll. Die Grafikprogrammierung in
Processing erfolgt normalerweise auf Basis eines fest vorgegebenen
\dqo{}Malbereichs\dqc{}. Wir wollen dem Benutzer aber die Wahl lassen,
diesen Bereich zunächst manuell durch die Größe des QML-Views
festzulegen. Wenn dann die gewünschte Fenstergröße festgelegt wurde,
kann der Benutzer im Processing-Code den gesamten QML-View für die
Darstellung verwenden. Der QML-Inhalt des Fensters passt sich durch
die Einstellung des Resize Mode immer an die Fenstergröße an.
Schließlich versuchen wir, bei Start der Anwendung im Vollbildmodus
den Ausgabebereich des QML-Views auf ein OpenGL-Widget zu lenken. Dazu
erzeugen wir eine solches Widget aus der Klasse \ospcmd{QGLWiget} und
setzen das erzeugte Objekt per Methode \ospcmd{setViewport()} als
Darstellungsfläche des QML-Views. Anschließend wird die Anwendung per
\ospcmd{showFullScreen()} im Vollbildmodus gestartet.
\ospsubsection{Der QML-View der Anwendung}{Der QML-View der Anwendung}{sec:webkit3qml}
Der QML-Code befindet sich wieder in der Datei \ospcmd{main.qml}. Der
Kopf der Datei ist den bisherigen QML-Beispielen sehr ähnlich: Es wird
ein Rechteck als Haupt-QML-Element deklariert; es hat die Größe
640 mal 480 Pixel und einen schwarzen Hintergrund:
\begin{osplisting}{QML}{Kopf der QML-Hauptdatei}{code:webkit3_qml1}
import Qt 4.7
import QtWebKit 1.0
Rectangle {
id: mainScreen
width: 640
height: 480
color: "black"
\end{osplisting}
Danach deklarieren wir ein Element für den gesamten Code-Editor
als \ospcmd{Item}. Diese Gruppierung aller Element des Editors ist
notwendig, da wir ja später zwischen dem Editor und dem ausführenden
Web-View hin und her schalten wollen:
\begin{ospsimplelisting}
Item {
id: codeView
anchors.fill: parent
\end{ospsimplelisting}
\ospsubsubsection{Die Kopfleiste}{Die Kopfleiste}{sec:webkit3header}
\index{QML!Kopfleiste}%
\index{QML!Hyperlinks}%
Dann folgt schon die im Screenshot in Abbildung
\ospfigref{fig:webkit3_qml_app} sichtbare obere Leiste: Links steht
die jeweils aktuelle Fenstergröße (damit der Benutzer die Größe des
Processing-Malbereichs anpassen kann), rechts ein Link \ospmenu{Über}
auf die Projektwebseite. Das Ganze wird als \ospcmd{Rectangle}-Element
deklariert, das zwei \ospcmd{Text}-Elemente enthält, und zwar alles
innerhalb des gestarteten \ospcmd{Item} mit der ID \ospcmd{codeView}:
\begin{osplisting}{QML}{Kopfleiste des Hauptfensters}{code:webkit3_qml2}
Rectangle {
width: parent.width
Text {
anchors.left: parent.left
horizontalAlignment: TextEdit.AlignLeft
color: "white"
text: mainScreen.width + " x " + mainScreen.height
}
Text {
anchors.right: parent.right
anchors.rightMargin: 20
horizontalAlignment: TextEdit.AlignRight
color: "blue"
text: "<a href=\"https://github.com/pbouda/Process-\">Über</a>"
onLinkActivated: Qt.openUrlExternally(link)
}
}
\end{osplisting}
Die beiden Text-Elemente sind jeweils per \ospcmd{horizontalAlignment}
am linken bzw. rechten Rand ausgerichtet. Das zweite Text-Element
zeigt, dass sich der Text einfach per HTML-Tags auszeichnen lässt. In
diesem Fall soll eben der Link auf die Projektwebseite dargestellt
werden. Allerdings muss man diesen Link noch aktivieren, was über den
Event-Handler \ospcmd{onLinkAc\-tivated} geschieht. Bei Klick auf den
Link wird dann die Funktion \ospcmd{open\-UrlExternally()} aufgerufen,
eine Hilfsfunktion im QML-Modul \ospcmd{Qt}. Die Variable
\ospcmd{link} stellt der Event-Handler zur Verfügung, der Inhalt ist
die URL des jeweils angeklickten Links.
\ospsubsubsection{Der Code-Editor}{Der Code-Editor}{sec:webkit3codeeditor}
Im nächsten Schritt werden bereits Code-Editor und Button
deklariert. Dazu erzeugen wir zunächst ein Rechteck, um den im
Screenshot sichtbaren grauen Rahmen sowie den hellgrauen Hintergrund
für beide Elemente zeichnen zu können. Dazu dienen folgende Angaben:
\begin{osplisting}{QML}{Rechteck um Code-Editor und Button}{code:webkit3_qml3}
Rectangle {
id: codeViewGroup
anchors.fill: parent
anchors.margins: 20
focus: true
color: "lightgrey"
border.color: "darkgrey"
border.width: 5
radius: 10
\end{osplisting}
Anschließend gruppieren wir die beiden Elemente Editor und Button noch
einmal durch ein \ospcmd{Item}-Element. Das hat hier ausschließlich
Layout"=Gründe, da wir so am einfachsten beide Elemente
direkt im übergeordneten Rechteck zentrieren können. Das \ospcmd{Item}
hat darum auch keine ID, sondern nur eine Angabe für die
horizontale Position:
\begin{osplisting}{QML}{Element zur Gruppierung von Editor und Button}{code:webkit3_qml4}
Item {
anchors.fill: parent
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
\end{osplisting}
Nun erst folgt der Editor. Das zentrale Element wird ein
\ospcmd{TextEdit} sein, allerdings müssen wir zur schöneren
Darstellung und um das Scrollen im Editor zu ermöglichen das Element
wiederum in zwei übergeordnete Elemente einschließen. Für das erste
reicht ein Rechteck, dem wir Rahmen und Abstände zuweisen. Das
Scrollen ermöglichen wir durch ein Element \ospcmd{Flickable}. Ohne
\ospcmd{Flickable} würde längerer Code einfach aus dem Editor unten
verschwinden, ohne dass der Benutzer die Möglichkeit hätte, den Code
jemals zu Gesicht zu bekommen. Zum einen wollen wir dem Benutzer das
manuelle Scrollen im Editor erlauben. Das Element \ospcmd{Flickable}
ist an den Einsatz auf Touchscreens angepasst, so dass auf dem Desktop
zunächst nur mit Maustaste-Klicken-Halten-Und-Verschieben gescrollt
werden kann. Das ist für unseren Fall zunächst ausreichend. Außerdem
wollen wir aber automatisch Scrollen, sobald der Cursor beim Schreiben
unten oder oben den Bildschirm verlässt. Wir müssen also bei einer
Änderung der Cursor-Position dafür sorgen, dass der Cursor immer im
Editor sichtbar ist.
Zuerst erfolgt aber die Deklaration des Rechtecks, damit der Editor
einen vernünftigen Abstand zum umgebenden Rechteck und zum Button
sowie einen eigenen Rahmen erhält:
\begin{osplisting}{QML}{Das Rechteck um den Editor}{code:webkit3_qml5}
Rectangle {
id: code
anchors.fill: parent
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 20
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.bottomMargin: 80
border.color: "grey"
border.width: 2
\end{osplisting}
\index{QML!Flickable}%
Innerhalb dieses Rechtecks befindet sich nun das
\ospcmd{Flickable}-Element, das seinen Inhalt zum Scrollen verfügbar
macht. Dazu müssen Höhe und Breite des Inhalts definiert werden,
in unserem Fall sind das genau Höhe und Breite des später zu
deklarierenden \ospcmd{TextEdit}-Elements. Dazu greifen wir auf die
Eigenschaften \ospcmd{paintedWidth} und \ospcmd{paintedHeight} dieses
Elements zu, das uns die genaue Höhe und Breite auf dem Bildschirm
liefert. Außerdem definieren wir für das \ospcmd{Flickable} gleich
eine Funktion \ospcmd{ensureVisible()}, der ein beliebiges Element
übergeben werden kann. Die Funktion scrollt dann den Inhalt des
\ospcmd{Flickable} so weit, dass das übergebene Element sichtbar
bleibt:
\begin{osplisting}{QML}{Das Flickable mit Hilfsfunktion}{code:webkit3_qml6}
Flickable {
id: flick
width: parent.width; height: parent.height;
contentWidth: codeEditor.paintedWidth
contentHeight: codeEditor.paintedHeight
clip: true
function ensureVisible(r)
{
if (contentX >= r.x)
contentX = r.x;
else if (contentX+width <= r.x+r.width)
contentX = r.x+r.width-width;
if (contentY >= r.y)
contentY = r.y;
else if (contentY+height <= r.y+r.height)
contentY = r.y+r.height-height;
}
\end{osplisting}
Die Eigenschaft \ospcmd{clip} sorgt dafür, dass sich das Element
nicht über den eigenen Rand hinaus zeichnet. Bei großen
nicht-sichtbaren Bereichen hat das deutliche
Geschwindigkeitsvorteile. Die Funktion \ospcmd{ensureVisible()}
überprüft für das übergebene Element, ob sich dessen x- oder
y-Position außerhalb des dargestellten Bereichs befindet. Dieser
Bereich ist über die Eigenschaften \ospcmd{contentX} und
\ospcmd{contentY} abfragbar -- sie enthalten die jeweilige x- und
y-Koordinate des Inhalts von \ospcmd{Flickable} an dessen linker
oberer Ecke. Der rechte und untere Rand ist demnach über diese
Eigenschaften plus jeweils Breite oder Höhe des \ospcmd{Flickable} zu
berechnen. Liegt das Element außerhalb dieses Bereichs, wird der
Inhalt entsprechend verschoben, indem den Eigenschaften
\ospcmd{contentX} und \ospcmd{contentY} neue Werte zugewiesen werden.
Diese neuen Werte ergeben sich aus den Positionsangaben des an die
Funktion übergebenen Elements (also \ospcmd{r.x} und \ospcmd{r.y}).
Somit wird der Inhalt so weit verschoben, dass das Element gerade noch
in \ospcmd{Flickable} sichtbar ist. Wir werden diese Funktion dann
einem Event-Handler in \ospcmd{TextEdit} zuweisen und den Cursor als
immer sichtbar zu machendes Element übergeben.
\index{QML!TextEdit}%
Darüber hinaus müssen wir nur Angaben zum Layout des Text-Editors und
seines Inhalts machen, z.\,B. dass das Element die Höhe und Breite des
\ospcmd{Flickable} einnimmt und dass der Text ohne Formatierung in
einer bestimmten Schriftgröße dargestellt wird. Dazu deklarieren wir
innerhalb des \ospcmd{Flickable} folgendes Element:
\begin{osplisting}{QML}{Der Code-Editor}{code:webkit3_qml7}
TextEdit {
id: codeEditor
focus: true
width: flick.width
height: flick.height
wrapMode: TextEdit.Wrap